@plumbus/core 0.6.3 → 0.6.5

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 (196) hide show
  1. package/CHANGELOG.md +385 -0
  2. package/README.md +23 -13
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/ai/ai/security.d.ts +36 -0
  5. package/dist/ai/ai-service.d.ts.map +1 -1
  6. package/dist/ai/ai-service.js +36 -8
  7. package/dist/ai/ai-service.js.map +1 -1
  8. package/dist/ai/index.d.ts +1 -1
  9. package/dist/ai/index.d.ts.map +1 -1
  10. package/dist/ai/index.js +1 -1
  11. package/dist/ai/index.js.map +1 -1
  12. package/dist/ai/security.d.ts +9 -0
  13. package/dist/ai/security.d.ts.map +1 -1
  14. package/dist/ai/security.js +18 -1
  15. package/dist/ai/security.js.map +1 -1
  16. package/dist/ai/types/config.d.ts +11 -0
  17. package/dist/ai/types/entity.d.ts +15 -0
  18. package/dist/ai/types/fields.d.ts +51 -0
  19. package/dist/api/index.d.ts +2 -2
  20. package/dist/api/index.d.ts.map +1 -1
  21. package/dist/api/index.js +1 -1
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/api/route-generator.d.ts +17 -0
  24. package/dist/api/route-generator.d.ts.map +1 -1
  25. package/dist/api/route-generator.js +55 -2
  26. package/dist/api/route-generator.js.map +1 -1
  27. package/dist/cli/commands/api.d.ts +9 -0
  28. package/dist/cli/commands/api.d.ts.map +1 -1
  29. package/dist/cli/commands/api.js +10 -1
  30. package/dist/cli/commands/api.js.map +1 -1
  31. package/dist/cli/commands/init.js +1 -1
  32. package/dist/cli/mcp-serve-context.d.ts.map +1 -1
  33. package/dist/cli/mcp-serve-context.js +40 -35
  34. package/dist/cli/mcp-serve-context.js.map +1 -1
  35. package/dist/cli/templates/resources.d.ts.map +1 -1
  36. package/dist/cli/templates/resources.js +5 -6
  37. package/dist/cli/templates/resources.js.map +1 -1
  38. package/dist/config/loader.d.ts.map +1 -1
  39. package/dist/config/loader.js +37 -0
  40. package/dist/config/loader.js.map +1 -1
  41. package/dist/data/field-encryption.d.ts +17 -0
  42. package/dist/data/field-encryption.d.ts.map +1 -0
  43. package/dist/data/field-encryption.js +80 -0
  44. package/dist/data/field-encryption.js.map +1 -0
  45. package/dist/data/index.d.ts +2 -0
  46. package/dist/data/index.d.ts.map +1 -1
  47. package/dist/data/index.js +2 -0
  48. package/dist/data/index.js.map +1 -1
  49. package/dist/data/mask-fields.d.ts +17 -0
  50. package/dist/data/mask-fields.d.ts.map +1 -0
  51. package/dist/data/mask-fields.js +56 -0
  52. package/dist/data/mask-fields.js.map +1 -0
  53. package/dist/data/registry.d.ts +4 -0
  54. package/dist/data/registry.d.ts.map +1 -1
  55. package/dist/data/registry.js +6 -0
  56. package/dist/data/registry.js.map +1 -1
  57. package/dist/data/repository.d.ts +3 -0
  58. package/dist/data/repository.d.ts.map +1 -1
  59. package/dist/data/repository.js +123 -41
  60. package/dist/data/repository.js.map +1 -1
  61. package/dist/define/defineCapability.d.ts +2 -0
  62. package/dist/define/defineCapability.d.ts.map +1 -1
  63. package/dist/define/defineCapability.js.map +1 -1
  64. package/dist/define/defineTranslation.d.ts +39 -3
  65. package/dist/define/defineTranslation.d.ts.map +1 -1
  66. package/dist/define/defineTranslation.js +3 -0
  67. package/dist/define/defineTranslation.js.map +1 -1
  68. package/dist/errors/data-errors.d.ts +23 -0
  69. package/dist/errors/data-errors.d.ts.map +1 -0
  70. package/dist/errors/data-errors.js +45 -0
  71. package/dist/errors/data-errors.js.map +1 -0
  72. package/dist/errors/index.d.ts +3 -11
  73. package/dist/errors/index.d.ts.map +1 -1
  74. package/dist/errors/index.js +3 -16
  75. package/dist/errors/index.js.map +1 -1
  76. package/dist/errors/plumbus-error.d.ts +13 -0
  77. package/dist/errors/plumbus-error.d.ts.map +1 -0
  78. package/dist/errors/plumbus-error.js +15 -0
  79. package/dist/errors/plumbus-error.js.map +1 -0
  80. package/dist/execution/capability-executor.d.ts.map +1 -1
  81. package/dist/execution/capability-executor.js +142 -61
  82. package/dist/execution/capability-executor.js.map +1 -1
  83. package/dist/execution/capability-invocation.d.ts +3 -1
  84. package/dist/execution/capability-invocation.d.ts.map +1 -1
  85. package/dist/execution/capability-invocation.js +41 -2
  86. package/dist/execution/capability-invocation.js.map +1 -1
  87. package/dist/execution/context-deps.d.ts +23 -0
  88. package/dist/execution/context-deps.d.ts.map +1 -0
  89. package/dist/execution/context-deps.js +45 -0
  90. package/dist/execution/context-deps.js.map +1 -0
  91. package/dist/execution/context-factory.d.ts.map +1 -1
  92. package/dist/execution/context-factory.js +9 -1
  93. package/dist/execution/context-factory.js.map +1 -1
  94. package/dist/execution/deferred-flow-service.d.ts +9 -0
  95. package/dist/execution/deferred-flow-service.d.ts.map +1 -0
  96. package/dist/execution/deferred-flow-service.js +54 -0
  97. package/dist/execution/deferred-flow-service.js.map +1 -0
  98. package/dist/execution/index.d.ts +4 -0
  99. package/dist/execution/index.d.ts.map +1 -1
  100. package/dist/execution/index.js +2 -0
  101. package/dist/execution/index.js.map +1 -1
  102. package/dist/execution/transactional-outbox.d.ts +33 -0
  103. package/dist/execution/transactional-outbox.d.ts.map +1 -0
  104. package/dist/execution/transactional-outbox.js +84 -0
  105. package/dist/execution/transactional-outbox.js.map +1 -0
  106. package/dist/flows/engine.d.ts +1 -0
  107. package/dist/flows/engine.d.ts.map +1 -1
  108. package/dist/flows/engine.js +1 -1
  109. package/dist/flows/engine.js.map +1 -1
  110. package/dist/flows/flow-service.d.ts.map +1 -1
  111. package/dist/flows/flow-service.js +2 -2
  112. package/dist/flows/flow-service.js.map +1 -1
  113. package/dist/governance/index.d.ts +1 -1
  114. package/dist/governance/index.d.ts.map +1 -1
  115. package/dist/governance/index.js +1 -1
  116. package/dist/governance/index.js.map +1 -1
  117. package/dist/index.d.ts +10 -10
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +8 -8
  120. package/dist/index.js.map +1 -1
  121. package/dist/jobs/deferred-job-dispatch-service.d.ts +7 -0
  122. package/dist/jobs/deferred-job-dispatch-service.d.ts.map +1 -0
  123. package/dist/jobs/deferred-job-dispatch-service.js +16 -0
  124. package/dist/jobs/deferred-job-dispatch-service.js.map +1 -0
  125. package/dist/jobs/dispatch.d.ts.map +1 -1
  126. package/dist/jobs/dispatch.js +8 -2
  127. package/dist/jobs/dispatch.js.map +1 -1
  128. package/dist/jobs/index.d.ts +3 -0
  129. package/dist/jobs/index.d.ts.map +1 -1
  130. package/dist/jobs/index.js +2 -0
  131. package/dist/jobs/index.js.map +1 -1
  132. package/dist/jobs/job-dispatch-service.d.ts +24 -0
  133. package/dist/jobs/job-dispatch-service.d.ts.map +1 -0
  134. package/dist/jobs/job-dispatch-service.js +26 -0
  135. package/dist/jobs/job-dispatch-service.js.map +1 -0
  136. package/dist/observability/index.d.ts +1 -1
  137. package/dist/observability/index.d.ts.map +1 -1
  138. package/dist/observability/index.js +1 -1
  139. package/dist/observability/index.js.map +1 -1
  140. package/dist/observability/metrics.d.ts +4 -0
  141. package/dist/observability/metrics.d.ts.map +1 -1
  142. package/dist/observability/metrics.js +19 -1
  143. package/dist/observability/metrics.js.map +1 -1
  144. package/dist/runtime/bootstrap.d.ts +2 -0
  145. package/dist/runtime/bootstrap.d.ts.map +1 -1
  146. package/dist/runtime/bootstrap.js +3 -1
  147. package/dist/runtime/bootstrap.js.map +1 -1
  148. package/dist/runtime/register-consumers.d.ts.map +1 -1
  149. package/dist/runtime/register-consumers.js +12 -16
  150. package/dist/runtime/register-consumers.js.map +1 -1
  151. package/dist/runtime/start-worker-pool.d.ts.map +1 -1
  152. package/dist/runtime/start-worker-pool.js +5 -1
  153. package/dist/runtime/start-worker-pool.js.map +1 -1
  154. package/dist/runtime/startup-warnings.d.ts +8 -0
  155. package/dist/runtime/startup-warnings.d.ts.map +1 -0
  156. package/dist/runtime/startup-warnings.js +26 -0
  157. package/dist/runtime/startup-warnings.js.map +1 -0
  158. package/dist/server/bootstrap.d.ts +1 -1
  159. package/dist/server/bootstrap.d.ts.map +1 -1
  160. package/dist/server/bootstrap.js +49 -23
  161. package/dist/server/bootstrap.js.map +1 -1
  162. package/dist/testing/e2e.d.ts.map +1 -1
  163. package/dist/testing/e2e.js +4 -1
  164. package/dist/testing/e2e.js.map +1 -1
  165. package/dist/translations/index.d.ts +2 -0
  166. package/dist/translations/index.d.ts.map +1 -1
  167. package/dist/translations/index.js +10 -0
  168. package/dist/translations/index.js.map +1 -1
  169. package/dist/types/capability.d.ts +5 -0
  170. package/dist/types/capability.d.ts.map +1 -1
  171. package/dist/types/config.d.ts +11 -0
  172. package/dist/types/config.d.ts.map +1 -1
  173. package/dist/types/context.d.ts +37 -1
  174. package/dist/types/context.d.ts.map +1 -1
  175. package/dist/worker/bootstrap.d.ts.map +1 -1
  176. package/dist/worker/bootstrap.js +25 -37
  177. package/dist/worker/bootstrap.js.map +1 -1
  178. package/dist/worker/index.d.ts +1 -1
  179. package/dist/worker/index.d.ts.map +1 -1
  180. package/dist/worker/index.js +1 -1
  181. package/dist/worker/index.js.map +1 -1
  182. package/instructions/README.md +38 -0
  183. package/instructions/ai.md +68 -20
  184. package/instructions/api.md +2 -0
  185. package/instructions/capabilities.md +19 -1
  186. package/instructions/cli.md +68 -2
  187. package/instructions/events.md +2 -0
  188. package/instructions/framework.md +5 -1
  189. package/instructions/governance.md +2 -0
  190. package/instructions/patterns.md +1 -1
  191. package/instructions/peer-dependencies.md +17 -5
  192. package/instructions/prompts.md +84 -0
  193. package/instructions/security.md +27 -15
  194. package/instructions/testing.md +45 -32
  195. package/instructions/translations.md +21 -2
  196. package/package.json +6 -5
@@ -0,0 +1,38 @@
1
+ # @plumbus/core — Agent Instructions
2
+
3
+ This folder ships with the npm tarball. It is the entry point for AI coding agents working in a Plumbus consumer app. Read these files when implementing framework primitives, CLI workflows, security, testing, or optional add-on integration.
4
+
5
+ For conceptual reference, see `docs/` in the Plumbus monorepo. These files are **prescriptive** (do this, don't do that).
6
+
7
+ | File | When to read |
8
+ |------|--------------|
9
+ | [guardrails.md](./guardrails.md) | First. Framework-first rules, forbidden escape hatches, git safety. |
10
+ | [framework.md](./framework.md) | Core abstractions, `ctx`, consumption surfaces, project layout, server hooks. |
11
+ | [capabilities.md](./capabilities.md) | `defineCapability()`, kinds, access, effects, MCP/API exposure. |
12
+ | [entities.md](./entities.md) | `defineEntity()`, fields, classification, tenant scoping. |
13
+ | [events.md](./events.md) | `defineEvent()`, outbox pattern, consumers. |
14
+ | [flows.md](./flows.md) | `defineFlow()`, steps, triggers, retries. |
15
+ | [prompts.md](./prompts.md) | `definePrompt()`, `system`/`description`, model resolution. |
16
+ | [ai.md](./ai.md) | `ctx.ai` operations, cost tracking, env-based provider config. |
17
+ | [translations.md](./translations.md) | `defineTranslation()`, `ctx.translations`. |
18
+ | [security.md](./security.md) | Access policies, tenant isolation, auth adapters. |
19
+ | [governance.md](./governance.md) | Advisory rules, `plumbus verify`, compliance profiles. |
20
+ | [testing.md](./testing.md) | `runCapability`, `simulateFlow`, `mockAI`, security asserts. |
21
+ | [patterns.md](./patterns.md) | Naming conventions, do's/don'ts, common recipes. |
22
+ | [cli.md](./cli.md) | CLI commands (dev, migrate, generate, test, etc.). |
23
+ | [mcp.md](./mcp.md) | Core MCP surface (`exposeAs`, `plumbus mcp *`). |
24
+ | [api.md](./api.md) | Core partner API surface (`exposeAs: ['api']`). |
25
+ | [deployment.md](./deployment.md) | Production deployment, workers, health, env vars. |
26
+ | [peer-dependencies.md](./peer-dependencies.md) | **Before editing add-on `peerDependencies`** — copy literals exactly. |
27
+ | [upgrading-0.5-capabilities.md](./upgrading-0.5-capabilities.md) | Canonical capability names, invoke policy, flow auth snapshot. |
28
+
29
+ Package quickstart: [../README.md](../README.md).
30
+
31
+ ## Critical rules
32
+
33
+ - **Primitives are mandatory architecture** — business logic belongs in capabilities, flows, entities, events, prompts, and translations.
34
+ - **Use `ctx.*` subsystems** — do not bypass the framework with ad hoc infrastructure unless documented.
35
+ - **Never edit `.plumbus/generated/`** — regenerate with `plumbus generate`.
36
+ - **Do not install framework-provided deps** — import Zod from `@plumbus/core/zod`; run tests with `plumbus test`.
37
+ - **Mock AI by operation** — `mockAI({ generate, extract, classify, retrieve })`, not by prompt name.
38
+ - **MCP tool names are canonical** — `<domain>.<name>` (e.g. `billing.getRefund`).
@@ -27,7 +27,7 @@ export const summarizeTicket = definePrompt({
27
27
  });
28
28
  ```
29
29
 
30
- Prompts should NOT hardcode `provider` or model `name`. These are resolved at runtime through the configuration chain (see below). Only set `temperature` and `maxTokens` in the prompt definition.
30
+ Prompts should NOT hardcode `provider` or model `name` unless you have a deliberate per-prompt override. Prefer env-based resolution (`AI_DEFAULT_*`, `PROMPT_{NAME}_*`) or `resolveAiOverrides` in `app/server.ts`. Only set `temperature` and `maxTokens` in the prompt definition when tuning is part of the contract. See `prompts.md` for `system`/`description` content.
31
31
 
32
32
  ## `ctx.ai` Operations
33
33
 
@@ -74,15 +74,63 @@ const docs = await ctx.ai.retrieve({ query: "refund policy for enterprise custom
74
74
  // docs: Array<{ content: string, source: string, score: number, metadata?: Record<string, unknown> }>
75
75
  ```
76
76
 
77
+ Ingest documents with `plumbus rag ingest <path>` (see `cli.md`).
78
+
79
+ ### Generate With Usage
80
+
81
+ Returns validated output plus token counts and per-request cost:
82
+
83
+ ```ts
84
+ const { data, usage, model, provider, cost } = await ctx.ai.generateWithUsage({
85
+ prompt: "summarizeTicket",
86
+ input: { ticketText: input.text, customerTier: "enterprise" },
87
+ });
88
+ ```
89
+
90
+ ### Stream Generate
91
+
92
+ Stream partial output for long-running generations:
93
+
94
+ ```ts
95
+ for await (const event of ctx.ai.streamGenerate({
96
+ prompt: "writeChapter",
97
+ input: { topic: input.topic },
98
+ })) {
99
+ if (event.type === "delta") process.stdout.write(event.text);
100
+ }
101
+ ```
102
+
103
+ ### Multi-turn messages
104
+
105
+ Pass explicit chat messages instead of a named prompt:
106
+
107
+ ```ts
108
+ await ctx.ai.generate({
109
+ messages: [
110
+ { role: "system", content: "You are a helpful assistant." },
111
+ { role: "user", content: input.question },
112
+ ],
113
+ });
114
+ ```
115
+
116
+ ### Cost helpers
117
+
118
+ - `ctx.ai.checkProviderCostBudget()` — pre-flight budget check before provider calls
119
+ - `ctx.ai.recordProviderCost()` — record spend from external provider usage APIs
120
+
77
121
  ## Output Validation
78
122
 
79
123
  All AI responses are validated against the prompt's output Zod schema. On failure, the framework retries generation (configurable). If retries are exhausted, a structured error is raised.
80
124
 
81
125
  ## Cost Tracking
82
126
 
83
- Every AI invocation records: model used, input/output token counts, latency, and prompt name. Token counts come directly from provider responses and are always accurate.
127
+ Every AI invocation records: model used, input/output token counts, latency, and prompt name. Token counts come directly from provider responses.
128
+
129
+ **Per-request cost** is computed automatically from a built-in pricing table (`calculateModelCost`) covering major OpenAI and Anthropic models. Unknown models (Ollama, custom endpoints) return cost `$0`. Cached-token and long-context adjustments are applied when providers return cache metadata.
84
130
 
85
- **Actual costs** are fetched from provider usage APIs — the framework never guesses or hardcodes pricing. Use `createUsageAPIClient()` to configure OpenAI and Anthropic billing API access:
131
+ `generateWithUsage()` and `streamGenerate()` expose `cost` on each call. Budget limits (`BudgetConfig`) can gate requests by tokens or dollar amounts.
132
+
133
+ **Optional billing reconciliation:** use `createUsageAPIClient()` + `createCostTracker().syncCosts()` to pull actual invoices from provider billing APIs — this supplements, but does not replace, per-request cost from the pricing table:
86
134
 
87
135
  ```ts
88
136
  import { createUsageAPIClient, createCostTracker } from "@plumbus/core";
@@ -92,24 +140,10 @@ const openaiUsage = createUsageAPIClient({
92
140
  apiKey: process.env.AI_OPENAI_API_KEY,
93
141
  });
94
142
 
95
- const anthropicUsage = createUsageAPIClient({
96
- provider: "anthropic",
97
- apiKey: process.env.AI_ANTHROPIC_API_KEY,
98
- });
99
-
100
- const costTracker = createCostTracker(budgetConfig, [openaiUsage, anthropicUsage]);
101
-
102
- // Fetch actual costs from billing APIs
143
+ const costTracker = createCostTracker(budgetConfig, [openaiUsage]);
103
144
  const result = await costTracker.syncCosts();
104
- if (!result.synced) {
105
- console.warn("Cost sync failed:", result.error);
106
- }
107
145
  ```
108
146
 
109
- If the usage API is not configured or unavailable, `cost` on each record is `null` and dollar-based budget limits cannot be enforced. Token-based budget limits always work.
110
-
111
- Budget limits (per-request token limit, daily cost limit, per-tenant daily limit) are enforced via `BudgetConfig`.
112
-
113
147
  ## Configuration via Environment Variables
114
148
 
115
149
  Use `loadConfig()` from `@plumbus/core` in your `config/app.config.ts` — it reads AI provider settings from env vars automatically.
@@ -125,7 +159,7 @@ AI_MODEL=gpt-4o-mini
125
159
 
126
160
  ### Multi-Provider
127
161
 
128
- Set `AI_DEFAULT_PROVIDER` to enable multi-provider mode. Providers are discovered from `AI_{NAME}_API_KEY` patterns:
162
+ Set `AI_DEFAULT_PROVIDER` to enable multi-provider mode. Env-based discovery supports **only** `AI_OPENAI_*` and `AI_ANTHROPIC_*` slots — other `AI_{NAME}_API_KEY` values are ignored with a warning. Wire additional providers programmatically via `createProviderAdapter` / `createAIService`.
129
163
 
130
164
  ```bash
131
165
  AI_DEFAULT_PROVIDER=openai
@@ -204,6 +238,20 @@ The framework calls this hook before each `generate()`, `generateWithUsage()`, a
204
238
 
205
239
  ## Security
206
240
 
207
- - Classified fields in prompt inputs are detected and warn/redact based on entity field classifications
241
+ Classified-field scanning is **opt-in**. It runs only when `aiProviders.security` is set in config or via `AI_SECURITY_*` env vars. Omit the block entirely to leave AI calls unscanned (capability `access` policies still apply).
242
+
243
+ When configured:
244
+
245
+ | Mode | Behavior |
246
+ |------|----------|
247
+ | `redact` (default) | Warn at `warnThreshold`; replace fields at/above `redactThreshold` with `[REDACTED]` and continue |
248
+ | `block` | Abort the AI call when any field at/above `warnThreshold` is detected (`AISecurityBlockedError`) |
249
+
250
+ Entity definitions are merged from the registry when the `security` block is present. Thresholds: `warnThreshold` / `redactThreshold` on config, or `AI_SECURITY_WARN_THRESHOLD` / `AI_SECURITY_REDACT_THRESHOLD`. Invalid `AI_SECURITY_MODE` values warn at load and fall back to `redact`.
251
+
252
+ Also:
253
+
208
254
  - Tenant isolation is enforced — prompts cannot access cross-tenant data
209
255
  - All AI invocations are recorded in the audit trail
256
+
257
+ See `docs/ai/ai-integration.md` and `docs/upgrading-contract-alignment.md` §12.
@@ -16,4 +16,6 @@ Plumbus capabilities can be exposed to partners on versioned HTTP routes. **Cont
16
16
  - **`plumbus api validate` / `generate` / `diff` / `test-fixtures validate`** — CLI entry points; dynamic import of `@plumbus/api`.
17
17
  - **`registerApiRoutes()`** — mount partner routes at app bootstrap (requires `@plumbus/api` install).
18
18
 
19
+ `plumbus api validate` exits `1` only on hard findings by default; pass `--fail-on-governance` to also fail on advisory API governance signals. Details: `node_modules/@plumbus/api/instructions/manifest-and-cli.md`.
20
+
19
21
  Install the runtime when serving partners: `pnpm add @plumbus/api` (optional peer of `@plumbus/core`, version-locked `0.1.x`).
@@ -119,6 +119,22 @@ Every capability must declare its side effects in the `effects` field:
119
119
 
120
120
  Effects are used by governance rules to analyze the system.
121
121
 
122
+ ## Transactional outbox (default ON)
123
+
124
+ For `kind: 'action'` and `kind: 'eventHandler'`, handler execution and output validation run inside a single database transaction by default. Entity writes via `ctx.data.*` and `ctx.events.emit()` outbox inserts commit or roll back together.
125
+
126
+ **Auto-excluded** (non-transactional): `kind: 'job'`, `effects.ai: true`, non-empty `effects.external`, and other kinds (e.g. `query`).
127
+
128
+ **Recommended:** keep the default ON and fix handlers that assumed partial commits. There is no framework-wide legacy/soft mode.
129
+
130
+ **Opt out (per app or capability — not the long-term default):**
131
+ - Globally: `execution.transactionalOutbox: false` in config, or `PLUMBUS_TRANSACTIONAL_OUTBOX=false`
132
+ - Per capability: `transactional: false` on `defineCapability({ ... })`
133
+
134
+ Inside an active transaction, `ctx.flows.start()` and `ctx.jobs.enqueue()` defer until after commit (pre-allocated ids). Nested success audits also defer until commit. Prefer `transactional: false` on parents that invoke AI capabilities — otherwise the parent transaction is held open for the LLM call (one-time warn).
135
+
136
+ See `docs/upgrading-contract-alignment.md` (Migration stance + §1) and `docs/architecture/execution-lifecycle.md`.
137
+
122
138
  ## Capability-to-capability invocation
123
139
 
124
140
  The only sanctioned path for synchronous composition is `ctx.capabilities.invoke`:
@@ -205,7 +221,9 @@ export const getRefund = defineCapability({
205
221
  ```
206
222
 
207
223
  **Rules when MCP-exposed:**
208
- - Only `kind: "query"` and `kind: "action"` may be exposed. `job` and `eventHandler` are rejected at `defineCapability()` time.
224
+ - `kind: "query"` and `kind: "action"` are standard MCP tools.
225
+ - `kind: "job"` is exposed via MCP Tasks (`tools/call` + `_meta.taskMetadata`). Register `mcpTaskEntity` in the app entity list.
226
+ - Only `kind: "eventHandler"` is rejected at `defineCapability()` time for MCP exposure.
209
227
  - Either `description`, `mcp.description`, or `explanation.summary` is required.
210
228
  - Agent identity is controlled via `access.serviceAccounts` and `access.scopes`. Deny-by-default still holds.
211
229
 
@@ -11,7 +11,8 @@ plumbus create <project-name> [--auth jwt|auth0|clerk] [--ai openai|anthropic] [
11
11
  # Create a monorepo project (backend / frontend / libs)
12
12
  plumbus create <project-name> --monorepo [--auth jwt|auth0|clerk] [--ai openai|anthropic] [--compliance GDPR|HIPAA] [--skip-install] [--git]
13
13
 
14
- # Initialize AI agent wiring (copilot, cursor, or agents-md)
14
+ # Initialize AI agent wiring (all formats by default; use --agent to narrow)
15
+ plumbus init
15
16
  plumbus init --agent copilot
16
17
 
17
18
  # Check project health
@@ -83,8 +84,31 @@ plumbus generate
83
84
  # Run governance checks (advisory signals, not hard blocks)
84
85
  plumbus verify
85
86
  plumbus verify --json
87
+
88
+ # Partner API contract validation (requires @plumbus/api)
89
+ plumbus api validate
90
+ plumbus api validate --fail-on-governance # also fail on advisory apiRules
91
+
92
+ # Compliance profile assessment
93
+ plumbus certify policy soc2
94
+ plumbus certify policy gdpr --json
95
+ ```
96
+
97
+ `plumbus verify` and `plumbus api validate` are both advisory for governance by default. Use `--fail-on-governance` on `api validate` when CI should gate on advisory API rules.
98
+
99
+ ## Testing
100
+
101
+ Consumer apps run tests through the framework — do not install vitest directly.
102
+
103
+ ```bash
104
+ plumbus test
105
+ plumbus test --watch
106
+ plumbus test --all # unit tests, then e2e
107
+ plumbus e2e # Playwright browser suites
86
108
  ```
87
109
 
110
+ Full command reference: `docs/cli/commands.md` in the framework repo (or browse the published docs).
111
+
88
112
  ## Database Management
89
113
 
90
114
  **All database operations must go through the framework. Never use manual SQL DDL queries.**
@@ -117,6 +141,10 @@ plumbus migrate push --create-db
117
141
 
118
142
  # Rollback the last applied migration
119
143
  plumbus migrate rollback
144
+
145
+ # Adopt migration history when the live schema already matches (drift recovery)
146
+ plumbus migrate reconcile
147
+ plumbus migrate reconcile --dry-run
120
148
  ```
121
149
 
122
150
  All migration commands accept `--json` for machine-readable output.
@@ -125,7 +153,8 @@ All migration commands accept `--json` for machine-readable output.
125
153
  1. Define entities in `app/entities/` using `defineEntity()`
126
154
  2. Run `plumbus migrate generate` to produce SQL migration files in `drizzle/`
127
155
  3. Run `plumbus migrate apply` to execute pending migrations
128
- 4. For rapid development, use `plumbus migrate push` to sync schema directly (no migration files)
156
+ 4. If schema already exists but history is missing: `plumbus migrate reconcile` (use `--dry-run` to preview)
157
+ 5. For rapid development, use `plumbus migrate push` to sync schema directly (no migration files)
129
158
 
130
159
  **Never run `drizzle-kit` manually** — the framework wraps it programmatically.
131
160
 
@@ -147,6 +176,15 @@ plumbus start --port 8080 --host 127.0.0.1
147
176
 
148
177
  Behind a load balancer set `TRUST_PROXY=true` (or a specific IP/CIDR) so Fastify trusts `X-Forwarded-*` headers. `app/server.ts` extension hooks (`onRoutesRegistered`, `resolveAiOverrides`, `onCapabilityError`, `onProcessError`, `onAICostRecorded`, `onFlowError`, `enableStrictStructuredOutputs`) are loaded automatically.
149
178
 
179
+ ## Background work
180
+
181
+ ```bash
182
+ plumbus worker start # split deployments
183
+ plumbus events status # outbox / queue health
184
+ plumbus flow dead-letter list # failed flow executions
185
+ plumbus seed # populate initial data
186
+ ```
187
+
150
188
  ## Translations
151
189
 
152
190
  ```bash
@@ -205,6 +243,34 @@ export default async function (ctx: { sql: any; args: string[] }) {
205
243
  }
206
244
  ```
207
245
 
246
+ ## RAG ingestion
247
+
248
+ ```bash
249
+ plumbus rag ingest <path> # Ingest a document into the RAG pipeline
250
+ plumbus rag ingest ./docs/guide.md --source support-docs --tenant-id <uuid>
251
+ ```
252
+
253
+ Requires AI provider credentials in config/env. See `docs/ai/ai-integration.md` for corpus retrieval from capabilities.
254
+
255
+ ## Framework upgrades
256
+
257
+ ```bash
258
+ plumbus upgrade # Migrate legacy UI artifacts after @plumbus/core / @plumbus/ui bumps
259
+ plumbus upgrade --dry-run # Report stale artifacts without writing
260
+ ```
261
+
262
+ `plumbus doctor` suggests `plumbus upgrade` when it detects stale generated frontend layout.
263
+
264
+ ## Agent brief sync
265
+
266
+ ```bash
267
+ plumbus agent sync # Regenerate .plumbus/briefs/project.md from discovered contracts
268
+ plumbus agent brief capability getUser # One-off brief for a single resource
269
+ plumbus agent brief entity User
270
+ ```
271
+
272
+ Run `plumbus agent sync` after adding capabilities/entities so coding agents see an up-to-date project brief. `plumbus init` creates wiring files but skips refreshing an existing brief — use sync for that.
273
+
208
274
  ## Important Notes
209
275
 
210
276
  - **Run from project root**: All commands must be executed from the consumer project directory where `package.json` lives.
@@ -47,6 +47,8 @@ handler: async (ctx, input) => {
47
47
 
48
48
  Events are **not** dispatched immediately. They are written to an outbox table in the **same database transaction** as the capability's data changes. This guarantees at-least-once delivery with no data/event inconsistency.
49
49
 
50
+ For `action` / `eventHandler` capabilities this is the default (**transactional outbox ON**). Opt out with `execution.transactionalOutbox: false`, `PLUMBUS_TRANSACTIONAL_OUTBOX=false`, or per-capability `transactional: false`. Auto-excluded: `kind: 'job'`, `effects.ai: true`, non-empty `effects.external`. See `capabilities.md` § Transactional outbox.
51
+
50
52
  A background dispatcher polls the outbox and publishes events to the queue system for delivery.
51
53
 
52
54
  ## Event Envelope
@@ -1,6 +1,6 @@
1
1
  # Plumbus Framework
2
2
 
3
- Plumbus is an AI-native, contract-driven TypeScript application framework. You build applications by composing six primitives — **Capabilities**, **Entities**, **Events**, **Flows**, **Prompts**, and **Translations** — through a controlled execution context (`ctx`). Two opt-in companion packages extend the surface: `@plumbus/chat` for conversational runtimes (the `Chat` primitive lives there) and `@plumbus/mcp` for serving capabilities to AI agents.
3
+ Plumbus is an AI-native, contract-driven TypeScript application framework. You build applications by composing six primitives — **Capabilities**, **Entities**, **Events**, **Flows**, **Prompts**, and **Translations** — through a controlled execution context (`ctx`). Optional add-on packages extend the surface when installed explicitly: `@plumbus/api` (partner HTTP API), `@plumbus/mcp` (AI agent tools), `@plumbus/chat` (+ `@plumbus/chat-ui`), `@plumbus/voice`, `@plumbus/knowledge-base`, `@plumbus/ui` (web UI generation), and `@plumbus/browser-extension`.
4
4
 
5
5
  ## Non-Negotiable Guardrails
6
6
 
@@ -39,6 +39,7 @@ Every capability handler receives `ctx` — the scoped runtime context:
39
39
  | `ctx.config` | Read-only application configuration |
40
40
  | `ctx.security` | Security service — policy evaluation and access enforcement |
41
41
  | `ctx.translations` | Localized string resolution — `ctx.translations.t("errors.notFound")` |
42
+ | `ctx.capabilities` | Cross-capability invocation — `ctx.capabilities.invoke('domain.name', input)` |
42
43
 
43
44
  ## Consumption Surfaces
44
45
 
@@ -47,6 +48,7 @@ Capabilities can be consumed by:
47
48
  | Surface | Default | Opt-in |
48
49
  |---------|---------|--------|
49
50
  | **HTTP** (REST) | Auto-generated for every `query` / `action` / `job` capability | — |
51
+ | **Partner API** | Off | `exposeAs: ['api']` + `@plumbus/api` + `registerApiRoutes()` in `app/server.ts` |
50
52
  | **Typed TS clients** (`@plumbus/ui`) | Generated by `plumbus ui generate` | — |
51
53
  | **MCP** (Model Context Protocol) | Off | Add `exposeAs: ['mcp']` to selected capabilities; install `@plumbus/mcp`; run `plumbus mcp serve` |
52
54
 
@@ -143,6 +145,8 @@ The optional `app/server.ts` file exports hooks that customize server behavior.
143
145
  | `onFlowError` | After a flow fails permanently (retries exhausted) | Logging flow failures to a system log table |
144
146
  | `onProcessError` | On uncaught exceptions, unhandled rejections, and Fastify-level errors | Logging process-level crashes that bypass capability/flow hooks |
145
147
  | `resolveAiOverrides` | Before each AI call | Dynamic model/provider configuration from DB |
148
+ | `onAICostRecorded` | After each AI cost record is written | Custom cost analytics or billing hooks |
149
+ | `enableStrictStructuredOutputs` | Server bootstrap | Global toggle for strict structured-output mode |
146
150
 
147
151
  ### Error Capture Coverage
148
152
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  Plumbus governance is **advisory, not blocking** — "guardrails, not gates." Rules produce warnings that inform developers; they don't prevent deployment.
4
4
 
5
+ The same model applies to `plumbus api validate`: governance signals print to stderr but do not fail the command unless you pass `--fail-on-governance`. Hard contract findings (manifest, policy, path params, fixtures) still exit `1` without that flag.
6
+
5
7
  ## Governance Signals
6
8
 
7
9
  ```ts
@@ -17,7 +17,7 @@ These rules are non-negotiable. They are architecture constraints, not style pre
17
17
  | Event | domain.pastTense | `order.placed`, `refund.requested`, `user.updated` |
18
18
  | Flow | camelCase noun | `refundApproval`, `orderFulfillment` |
19
19
  | Prompt | camelCase verb+noun | `summarizeTicket`, `classifyIntent` |
20
- | Domain | lowercase singular | `users`, `billing`, `fulfillment` |
20
+ | Domain | lowercase, one word (plural is common) | `users`, `billing`, `fulfillment` |
21
21
 
22
22
  ## File Structure
23
23
 
@@ -4,27 +4,39 @@ Read this file **before** editing `peerDependencies` in any `packages/*/package.
4
4
 
5
5
  ## CRITICAL rules
6
6
 
7
- 1. **Copy literals — do not derive ranges.** Never compute semver ranges from intuition. Copy the exact string from the table below or from a canonical `package.json` (`packages/mcp/package.json`, `packages/api/package.json`).
7
+ 1. **Copy literals — do not derive ranges.** Never compute semver ranges from intuition. Copy the exact string from the table below or from a canonical `package.json` (`packages/mcp/package.json`, `packages/api/package.json`, `packages/voice/package.json`).
8
8
  2. **Never use `^0.x` caret ranges** on `@plumbus/core` peers in add-on packages. npm treats `^0.5.0` as **0.5.x only** (`>=0.5.0 <0.6.0`), not "0.5 and above."
9
9
  3. **pnpm passing locally does not prove peers are correct.** Backend Docker images install production deps with **npm** (`deployment.md` Rule 7). Wrong peers break Docker builds even when `pnpm install` succeeds.
10
10
  4. **Do not copy from CHANGELOG history.** Older releases documented incorrect ranges (`^0.5.0 <0.7.0` claimed to support 0.6.x — it does not under npm). Use this file and the canonical `package.json` files instead.
11
11
 
12
- ## Canonical `@plumbus/core` peer strings
12
+ ## Canonical `@plumbus/core` peer strings (add-ons → core)
13
13
 
14
14
  | Package kind | `peerDependencies["@plumbus/core"]` | Canonical copy-from |
15
15
  |---|---|---|
16
16
  | Most add-ons (chat, chat-ui, knowledge-base, mcp, api, browser-extension) | `"0.5.x \|\| 0.6.x"` | `packages/mcp/package.json` |
17
- | Voice only (requires core 0.6+ media/cost APIs) | `"^0.6.0 <0.7.0"` | `packages/voice/package.json` |
17
+ | Voice only (requires core 0.6+ media/cost APIs) | `"0.6.x"` | `packages/voice/package.json` |
18
18
 
19
19
  When adding a **new** publishable add-on under `packages/`, use `"0.5.x || 0.6.x"` unless the package genuinely requires core 0.6+ only (then use the voice pattern).
20
20
 
21
+ ## Other publishable peer strings
22
+
23
+ | Declaring package | Peer target | Literal | Required? | Canonical copy-from |
24
+ |---|---|---|---|---|
25
+ | `@plumbus/core` | `@plumbus/mcp` | `"0.5.x \|\| 0.6.x"` | optional | `packages/plumbus-core/package.json` |
26
+ | `@plumbus/core` | `@plumbus/api` | `"0.1.x"` | optional | `packages/plumbus-core/package.json` |
27
+ | `@plumbus/chat` | `@plumbus/knowledge-base` | `"^0.1.0"` | optional | `packages/chat/package.json` |
28
+ | `@plumbus/chat-ui` | `@plumbus/chat` | `"0.1.x"` | required | `packages/chat-ui/package.json` |
29
+ | `@plumbus/chat-ui` | `@plumbus/core` | `"0.5.x \|\| 0.6.x"` | required | `packages/chat-ui/package.json` |
30
+
31
+ **Peering direction:** add-ons declare `@plumbus/core` as a peer — consumer apps install both. `@plumbus/core` optionally peers `@plumbus/mcp` and `@plumbus/api` when those packages are present. `@plumbus/chat` optionally peers `@plumbus/knowledge-base` for registry-backed context sources — **not** the reverse. `@plumbus/knowledge-base` only peers `@plumbus/core`.
32
+
21
33
  ## Forbidden patterns
22
34
 
23
35
  | Range | Why it is wrong |
24
36
  |---|---|
25
37
  | `"^0.5.0 <0.7.0"` | npm resolves `^0.5.0` to 0.5.x only — **rejects `@plumbus/core@0.6.0`** |
26
38
  | `"^0.5.0 <0.6.0"` | 0.5.x only — rejects 0.6.x |
27
- | `"^0.6.x"` or `"^0.6.0"` alone | Too loose or wrong syntax for multi-line support; use the table literals |
39
+ | `"^0.6.0 <0.7.0"` or `"^0.6.x"` on voice | Use the voice literal `"0.6.x"` from `packages/voice/package.json` |
28
40
  | Widening an upper bound on a caret range | e.g. changing `<0.6.0` to `<0.7.0` on `^0.5.0` does **not** add 0.6.x support |
29
41
 
30
42
  ## When `@plumbus/core` gets a new minor line (e.g. 0.7.0)
@@ -43,4 +55,4 @@ See also: `.agents/skills/bump-version/SKILL.md` (core **minor** bump checklist)
43
55
 
44
56
  - `deployment.md` Rule 7 — backend `proddeps` uses `npm install --omit=dev`
45
57
  - `.agents/skills/new-package-instructions/SKILL.md` — new package `package.json` peers
46
- - `packages/mcp/package.json` / `packages/api/package.json` — canonical examples
58
+ - `packages/mcp/package.json` / `packages/api/package.json` / `packages/voice/package.json` — canonical examples
@@ -0,0 +1,84 @@
1
+ # Prompts
2
+
3
+ Prompts are structured AI interaction templates defined with `definePrompt()`. They pair typed Zod `input`/`output` schemas with prompt text and model tuning.
4
+
5
+ ## Prompt content
6
+
7
+ The actual instructions sent to the provider live in two optional string fields:
8
+
9
+ | Field | Purpose |
10
+ |-------|---------|
11
+ | `system` | Stable provider system instructions (role, rules, output format) |
12
+ | `description` | User/data prompt content (the main task text) |
13
+
14
+ Both support simple top-level `{{key}}` substitution from the prompt `input` object.
15
+
16
+ ```ts
17
+ import { definePrompt } from "@plumbus/core";
18
+ import { z } from "@plumbus/core/zod";
19
+
20
+ export const classifyTicket = definePrompt({
21
+ name: "classifyTicket",
22
+ domain: "support",
23
+ system: "You classify support tickets. Return only the requested JSON fields.",
24
+ description: "Ticket text:\n{{ticketText}}\n\nCustomer tier: {{customerTier}}",
25
+ input: z.object({
26
+ ticketText: z.string(),
27
+ customerTier: z.enum(["free", "pro", "enterprise"]).optional(),
28
+ }),
29
+ output: z.object({
30
+ category: z.enum(["billing", "technical", "general"]),
31
+ priority: z.enum(["low", "medium", "high"]),
32
+ }),
33
+ model: {
34
+ temperature: 0.2,
35
+ maxTokens: 300,
36
+ },
37
+ });
38
+ ```
39
+
40
+ When `system` is omitted, the framework uses the prior single user-message behavior (description only).
41
+
42
+ ### Unsubstituted input
43
+
44
+ By default, any input keys not referenced as `{{key}}` in `system`/`description` are appended to the user message. Set `appendUnsubstitutedInput: false` to disable that behavior.
45
+
46
+ ## Model configuration
47
+
48
+ `model` is an optional `ModelConfig` object:
49
+
50
+ | Field | Type | Notes |
51
+ |-------|------|-------|
52
+ | `provider` | string? | Routes to a named provider in multi-provider config |
53
+ | `name` | string? | Model id (e.g. `gpt-4o-mini`) |
54
+ | `temperature` | number? | Sampling temperature |
55
+ | `maxTokens` | number? | Max output tokens |
56
+
57
+ **Recommended convention:** omit `provider` and `name` in the prompt definition and resolve them via env (`AI_DEFAULT_PROVIDER`, `AI_DEFAULT_MODEL`, `PROMPT_{NAME}_*`) or the `resolveAiOverrides` hook in `app/server.ts`. Set only `temperature` and `maxTokens` in the prompt when you want per-prompt tuning baked into the contract.
58
+
59
+ Resolution order: `resolveAiOverrides` → per-prompt env vars → prompt `model` fields → `AI_DEFAULT_MODEL` / `AI_DEFAULT_PROVIDER`.
60
+
61
+ ## Structured output and streaming flags
62
+
63
+ | Field | Effect |
64
+ |-------|--------|
65
+ | `disableStrictStructuredOutputs` | Opt out of strict JSON-schema mode for this prompt |
66
+ | `requireStrictStructuredOutputs` | Refuse to run unless a provider JSON Schema can be sent (no silent prompt-only JSON fallback) |
67
+ | `structuredOutputTransport` | How structured output is requested from the provider |
68
+ | `skipStreamValidationFallback` | On `streamGenerate`, do not fall back to non-streaming retry on validation failure |
69
+ | `disableTextModeBrevityHint` | Skip the automatic brevity hint for text-mode prompts |
70
+
71
+ See `docs/ai/ai-integration.md` for strict structured outputs and streaming behavior.
72
+
73
+ ## Invoking prompts
74
+
75
+ From capabilities, use `ctx.ai`:
76
+
77
+ ```ts
78
+ const result = await ctx.ai.generate({
79
+ prompt: "classifyTicket",
80
+ input: { ticketText: input.body, customerTier: "enterprise" },
81
+ });
82
+ ```
83
+
84
+ Also available: `generateWithUsage` (returns tokens + cost), `streamGenerate` (async iterator), `extract`, `classify`, `retrieve`. See `ai.md` for the full `ctx.ai` surface.
@@ -15,12 +15,14 @@ access: {
15
15
  ```
16
16
 
17
17
  ### Evaluation Order
18
- 1. If `public: true` → allow
19
- 2. Authenticate identity → populate `ctx.auth`
20
- 3. Check `roles` — user must have at least one matching role
21
- 4. Check `scopes` — user must have all required scopes
22
- 5. If `tenantScoped: true` — verify `ctx.auth.tenantId` matches target data
23
- 6. Check `serviceAccounts` — for service-to-service calls
18
+
19
+ 1. If no `access` policy → **deny**
20
+ 2. If `public: true` → allow
21
+ 3. Authenticate identity → populate `ctx.auth` (`userId` required for non-public)
22
+ 4. If `serviceAccounts` is set and `auth.userId` matches → **allow** (short-circuit; skips roles, scopes, and tenant checks)
23
+ 5. If `tenantScoped: true` on the capability → caller must have `tenantId` in `ctx.auth`
24
+ 6. Check `roles` — user must have at least one matching role (when `roles` is set)
25
+ 7. Check `scopes` — user must have all required scopes (when `scopes` is set)
24
26
 
25
27
  If any check fails → **403 Forbidden** with audit record.
26
28
 
@@ -47,17 +49,22 @@ If flow steps return **403** after upgrading to 0.5.x, audit `access.roles` on s
47
49
 
48
50
  ## Tenant Isolation
49
51
 
50
- When `tenantScoped: true`:
51
- - All repository queries automatically inject `WHERE tenantId = ctx.auth.tenantId`
52
- - Cross-tenant data access is blocked at the framework level
53
- - Events carry `tenantId` and are only delivered to matching consumers
52
+ **Entity-level filtering:** when an entity declares `tenantScoped: true`, repository queries automatically inject `WHERE tenantId = ctx.auth.tenantId`. Cross-tenant data access is blocked at the framework level.
53
+
54
+ **Capability-level gate:** the capability's `access.tenantScoped` flag requires the caller to carry a `tenantId` in `ctx.auth`. It does not turn on repository filtering by itself — that follows the entity definition.
55
+
56
+ **Cross-tenant admin bypass:** capabilities with `access.tenantScoped: false` pass `bypassTenantScope: true` into `createDependencies()`, allowing explicitly cross-tenant admin routes while role/scope checks still apply.
57
+
58
+ Events carry `tenantId` and are only delivered to matching consumers.
54
59
 
55
60
  ## Field Classification & Edit Zones
56
61
 
57
62
  Entity fields with `classification: "sensitive"` or `"highly_sensitive"`:
58
63
  - Are masked in logs and audit records
59
64
  - Trigger governance warnings if used in AI prompts
60
- - Can be flagged for encryption at rest
65
+ - Can be flagged for encryption at rest (`encrypted: true` + `PLUMBUS_ENCRYPTION_KEY`)
66
+
67
+ **AI prompt scanning** is separate and **opt-in**: configure `aiProviders.security` (or `AI_SECURITY_*` env) to warn/redact/block classified fields in `ctx.ai.*` inputs. Without that config, classification alone does not scan prompts. See `ai.md` § Security.
61
68
 
62
69
  ### Edit Zones
63
70
 
@@ -69,11 +76,16 @@ Entity fields with `classification: "sensitive"` or `"highly_sensitive"`:
69
76
 
70
77
  ## Authentication Adapters
71
78
 
72
- Plumbus uses pluggable auth adapters. The default is JWT validation. The adapter normalizes tokens into `AuthContext`:
79
+ Plumbus uses pluggable auth adapters. The adapter normalizes tokens into `AuthContext`:
80
+
81
+ - **JWT (default)** — `createJwtAdapter()` verifies HMAC-SHA256 tokens (`secret`, optional `issuer`/`audience`, `claimMapping`)
82
+ - **OIDC** — `createOidcAdapter()` verifies RS256/ES256 via JWKS (`issuer`, `audience`, optional `jwksUri`/`jwksCacheTtl`)
83
+ - **SAML 2.0** — `createSamlAdapter()` validates SAML assertions (`idpCertificate`, `issuer`, `audience`)
84
+ - **SCIM 2.0 provisioning** — `createScimService()` for IdP-driven user lifecycle (`createUser`, `patchUser`, etc.)
85
+ - **JWT signing** — `signJwt()` for first-party token issuance
86
+ - **Custom** — implement the `AuthAdapter` interface
73
87
 
74
- - JWT (default) — validates JWT tokens, extracts claims
75
- - Auth0, Clerk, Cognito — provider-specific adapters (community)
76
- - Custom — implement the adapter interface
88
+ See `docs/security/security-model.md` for full adapter examples.
77
89
 
78
90
  ## Password Utilities
79
91