@plumbus/core 0.6.4 → 0.6.6

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 (207) hide show
  1. package/CHANGELOG.md +400 -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/commands/translation.d.ts.map +1 -1
  33. package/dist/cli/commands/translation.js +13 -28
  34. package/dist/cli/commands/translation.js.map +1 -1
  35. package/dist/cli/commands/ui.d.ts +7 -4
  36. package/dist/cli/commands/ui.d.ts.map +1 -1
  37. package/dist/cli/commands/ui.js +28 -36
  38. package/dist/cli/commands/ui.js.map +1 -1
  39. package/dist/cli/mcp-serve-context.d.ts.map +1 -1
  40. package/dist/cli/mcp-serve-context.js +40 -35
  41. package/dist/cli/mcp-serve-context.js.map +1 -1
  42. package/dist/cli/scaffold-write.d.ts +1 -1
  43. package/dist/cli/scaffold-write.d.ts.map +1 -1
  44. package/dist/cli/scaffold-write.js +1 -1
  45. package/dist/cli/scaffold-write.js.map +1 -1
  46. package/dist/cli/templates/resources.d.ts.map +1 -1
  47. package/dist/cli/templates/resources.js +5 -6
  48. package/dist/cli/templates/resources.js.map +1 -1
  49. package/dist/config/loader.d.ts.map +1 -1
  50. package/dist/config/loader.js +37 -0
  51. package/dist/config/loader.js.map +1 -1
  52. package/dist/data/field-encryption.d.ts +17 -0
  53. package/dist/data/field-encryption.d.ts.map +1 -0
  54. package/dist/data/field-encryption.js +80 -0
  55. package/dist/data/field-encryption.js.map +1 -0
  56. package/dist/data/index.d.ts +2 -0
  57. package/dist/data/index.d.ts.map +1 -1
  58. package/dist/data/index.js +2 -0
  59. package/dist/data/index.js.map +1 -1
  60. package/dist/data/mask-fields.d.ts +17 -0
  61. package/dist/data/mask-fields.d.ts.map +1 -0
  62. package/dist/data/mask-fields.js +56 -0
  63. package/dist/data/mask-fields.js.map +1 -0
  64. package/dist/data/registry.d.ts +4 -0
  65. package/dist/data/registry.d.ts.map +1 -1
  66. package/dist/data/registry.js +6 -0
  67. package/dist/data/registry.js.map +1 -1
  68. package/dist/data/repository.d.ts +3 -0
  69. package/dist/data/repository.d.ts.map +1 -1
  70. package/dist/data/repository.js +123 -41
  71. package/dist/data/repository.js.map +1 -1
  72. package/dist/define/defineCapability.d.ts +2 -0
  73. package/dist/define/defineCapability.d.ts.map +1 -1
  74. package/dist/define/defineCapability.js.map +1 -1
  75. package/dist/errors/data-errors.d.ts +23 -0
  76. package/dist/errors/data-errors.d.ts.map +1 -0
  77. package/dist/errors/data-errors.js +45 -0
  78. package/dist/errors/data-errors.js.map +1 -0
  79. package/dist/errors/index.d.ts +3 -11
  80. package/dist/errors/index.d.ts.map +1 -1
  81. package/dist/errors/index.js +3 -16
  82. package/dist/errors/index.js.map +1 -1
  83. package/dist/errors/plumbus-error.d.ts +13 -0
  84. package/dist/errors/plumbus-error.d.ts.map +1 -0
  85. package/dist/errors/plumbus-error.js +15 -0
  86. package/dist/errors/plumbus-error.js.map +1 -0
  87. package/dist/execution/capability-executor.d.ts.map +1 -1
  88. package/dist/execution/capability-executor.js +142 -61
  89. package/dist/execution/capability-executor.js.map +1 -1
  90. package/dist/execution/capability-invocation.d.ts +3 -1
  91. package/dist/execution/capability-invocation.d.ts.map +1 -1
  92. package/dist/execution/capability-invocation.js +41 -2
  93. package/dist/execution/capability-invocation.js.map +1 -1
  94. package/dist/execution/context-deps.d.ts +23 -0
  95. package/dist/execution/context-deps.d.ts.map +1 -0
  96. package/dist/execution/context-deps.js +45 -0
  97. package/dist/execution/context-deps.js.map +1 -0
  98. package/dist/execution/context-factory.d.ts.map +1 -1
  99. package/dist/execution/context-factory.js +9 -1
  100. package/dist/execution/context-factory.js.map +1 -1
  101. package/dist/execution/deferred-flow-service.d.ts +9 -0
  102. package/dist/execution/deferred-flow-service.d.ts.map +1 -0
  103. package/dist/execution/deferred-flow-service.js +54 -0
  104. package/dist/execution/deferred-flow-service.js.map +1 -0
  105. package/dist/execution/index.d.ts +4 -0
  106. package/dist/execution/index.d.ts.map +1 -1
  107. package/dist/execution/index.js +2 -0
  108. package/dist/execution/index.js.map +1 -1
  109. package/dist/execution/transactional-outbox.d.ts +33 -0
  110. package/dist/execution/transactional-outbox.d.ts.map +1 -0
  111. package/dist/execution/transactional-outbox.js +84 -0
  112. package/dist/execution/transactional-outbox.js.map +1 -0
  113. package/dist/flows/engine.d.ts +1 -0
  114. package/dist/flows/engine.d.ts.map +1 -1
  115. package/dist/flows/engine.js +1 -1
  116. package/dist/flows/engine.js.map +1 -1
  117. package/dist/flows/flow-service.d.ts.map +1 -1
  118. package/dist/flows/flow-service.js +2 -2
  119. package/dist/flows/flow-service.js.map +1 -1
  120. package/dist/governance/index.d.ts +1 -1
  121. package/dist/governance/index.d.ts.map +1 -1
  122. package/dist/governance/index.js +1 -1
  123. package/dist/governance/index.js.map +1 -1
  124. package/dist/index.d.ts +12 -11
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +9 -10
  127. package/dist/index.js.map +1 -1
  128. package/dist/jobs/deferred-job-dispatch-service.d.ts +7 -0
  129. package/dist/jobs/deferred-job-dispatch-service.d.ts.map +1 -0
  130. package/dist/jobs/deferred-job-dispatch-service.js +16 -0
  131. package/dist/jobs/deferred-job-dispatch-service.js.map +1 -0
  132. package/dist/jobs/dispatch.d.ts.map +1 -1
  133. package/dist/jobs/dispatch.js +8 -2
  134. package/dist/jobs/dispatch.js.map +1 -1
  135. package/dist/jobs/index.d.ts +3 -0
  136. package/dist/jobs/index.d.ts.map +1 -1
  137. package/dist/jobs/index.js +2 -0
  138. package/dist/jobs/index.js.map +1 -1
  139. package/dist/jobs/job-dispatch-service.d.ts +24 -0
  140. package/dist/jobs/job-dispatch-service.d.ts.map +1 -0
  141. package/dist/jobs/job-dispatch-service.js +26 -0
  142. package/dist/jobs/job-dispatch-service.js.map +1 -0
  143. package/dist/observability/index.d.ts +1 -1
  144. package/dist/observability/index.d.ts.map +1 -1
  145. package/dist/observability/index.js +1 -1
  146. package/dist/observability/index.js.map +1 -1
  147. package/dist/observability/metrics.d.ts +4 -0
  148. package/dist/observability/metrics.d.ts.map +1 -1
  149. package/dist/observability/metrics.js +19 -1
  150. package/dist/observability/metrics.js.map +1 -1
  151. package/dist/runtime/bootstrap.d.ts +2 -0
  152. package/dist/runtime/bootstrap.d.ts.map +1 -1
  153. package/dist/runtime/bootstrap.js +3 -1
  154. package/dist/runtime/bootstrap.js.map +1 -1
  155. package/dist/runtime/register-consumers.d.ts.map +1 -1
  156. package/dist/runtime/register-consumers.js +12 -16
  157. package/dist/runtime/register-consumers.js.map +1 -1
  158. package/dist/runtime/start-worker-pool.d.ts.map +1 -1
  159. package/dist/runtime/start-worker-pool.js +5 -1
  160. package/dist/runtime/start-worker-pool.js.map +1 -1
  161. package/dist/runtime/startup-warnings.d.ts +8 -0
  162. package/dist/runtime/startup-warnings.d.ts.map +1 -0
  163. package/dist/runtime/startup-warnings.js +26 -0
  164. package/dist/runtime/startup-warnings.js.map +1 -0
  165. package/dist/server/bootstrap.d.ts +1 -1
  166. package/dist/server/bootstrap.d.ts.map +1 -1
  167. package/dist/server/bootstrap.js +49 -23
  168. package/dist/server/bootstrap.js.map +1 -1
  169. package/dist/testing/e2e.d.ts.map +1 -1
  170. package/dist/testing/e2e.js +4 -1
  171. package/dist/testing/e2e.js.map +1 -1
  172. package/dist/translations/index.d.ts +4 -0
  173. package/dist/translations/index.d.ts.map +1 -1
  174. package/dist/translations/index.js +12 -1
  175. package/dist/translations/index.js.map +1 -1
  176. package/dist/translations/status.d.ts +24 -0
  177. package/dist/translations/status.d.ts.map +1 -0
  178. package/dist/translations/status.js +35 -0
  179. package/dist/translations/status.js.map +1 -0
  180. package/dist/types/capability.d.ts +5 -0
  181. package/dist/types/capability.d.ts.map +1 -1
  182. package/dist/types/config.d.ts +11 -0
  183. package/dist/types/config.d.ts.map +1 -1
  184. package/dist/types/context.d.ts +37 -1
  185. package/dist/types/context.d.ts.map +1 -1
  186. package/dist/worker/bootstrap.d.ts.map +1 -1
  187. package/dist/worker/bootstrap.js +25 -37
  188. package/dist/worker/bootstrap.js.map +1 -1
  189. package/dist/worker/index.d.ts +1 -1
  190. package/dist/worker/index.d.ts.map +1 -1
  191. package/dist/worker/index.js +1 -1
  192. package/dist/worker/index.js.map +1 -1
  193. package/instructions/README.md +38 -0
  194. package/instructions/ai.md +68 -20
  195. package/instructions/api.md +2 -0
  196. package/instructions/capabilities.md +19 -1
  197. package/instructions/cli.md +68 -2
  198. package/instructions/events.md +2 -0
  199. package/instructions/framework.md +5 -1
  200. package/instructions/governance.md +2 -0
  201. package/instructions/patterns.md +1 -1
  202. package/instructions/peer-dependencies.md +17 -5
  203. package/instructions/prompts.md +84 -0
  204. package/instructions/security.md +27 -15
  205. package/instructions/testing.md +45 -32
  206. package/instructions/translations.md +3 -3
  207. package/package.json +5 -4
@@ -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
 
@@ -15,15 +15,19 @@ import { runCapability, simulateFlow, mockAI, createTestContext } from "@plumbus
15
15
  Execute a capability in an isolated test environment:
16
16
 
17
17
  ```ts
18
- const result = await runCapability(getUser, {
19
- input: { userId: "usr_123" },
20
- auth: { userId: "admin_1", roles: ["admin"], scopes: [], provider: "test" },
21
- data: {
22
- User: [{ id: "usr_123", name: "Alice", email: "alice@test.com" }],
18
+ const result = await runCapability(
19
+ getUser,
20
+ { userId: "usr_123" },
21
+ {
22
+ auth: { userId: "admin_1", roles: ["admin"], scopes: [], provider: "test" },
23
+ data: {
24
+ User: [{ id: "usr_123", name: "Alice", email: "alice@test.com" }],
25
+ },
23
26
  },
24
- });
27
+ );
25
28
 
26
- expect(result.name).toBe("Alice");
29
+ expect(result.success).toBe(true);
30
+ expect(result.data?.name).toBe("Alice");
27
31
  ```
28
32
 
29
33
  ### Paginated list capabilities
@@ -37,8 +41,10 @@ When testing `kind: 'query'` list endpoints that paginate via `findMany` + `coun
37
41
  ```ts
38
42
  const page1 = await runCapability(listStaff, { page: 1, limit: 2 }, { ctx });
39
43
  const page2 = await runCapability(listStaff, { page: 2, limit: 2 }, { ctx });
40
- expect(page1.items).not.toEqual(page2.items);
41
- expect(page1.total).toBeGreaterThan(page1.limit);
44
+ expect(page1.success && page1.data).toBeTruthy();
45
+ expect(page2.success && page2.data).toBeTruthy();
46
+ expect(page1.data.items).not.toEqual(page2.data.items);
47
+ expect(page1.data.total).toBeGreaterThan(page1.data.limit);
42
48
  ```
43
49
 
44
50
  ### `simulateFlow`
@@ -46,16 +52,21 @@ expect(page1.total).toBeGreaterThan(page1.limit);
46
52
  Simulate flow execution and inspect step history:
47
53
 
48
54
  ```ts
49
- const execution = await simulateFlow("refundApproval", {
50
- input: { refundId: "rf_1", amount: 50 },
51
- events: {
52
- "refund.approved": { refundId: "rf_1", approvedBy: "mgr_1" },
55
+ import { refundApproval } from "../refundApproval/flow.js";
56
+
57
+ const execution = await simulateFlow(
58
+ refundApproval,
59
+ { refundId: "rf_1", amount: 50 },
60
+ {
61
+ capabilityResults: {
62
+ "billing.validateRefund": { valid: true },
63
+ },
53
64
  },
54
- });
65
+ );
55
66
 
56
67
  expect(execution.status).toBe("completed");
57
- expect(execution.steps).toHaveLength(4);
58
- expect(execution.steps[0].name).toBe("validateRefund");
68
+ expect(execution.history.length).toBeGreaterThan(0);
69
+ expect(execution.history[0]?.stepName).toBe("validateRefund");
59
70
  ```
60
71
 
61
72
  ### `mockAI`
@@ -65,7 +76,7 @@ Mock AI provider responses:
65
76
  ```ts
66
77
  const ctx = createTestContext({
67
78
  ai: mockAI({
68
- summarizeTicket: { summary: "Customer wants refund", priority: "high", sentiment: "negative" },
79
+ generate: { summary: "Customer wants refund", priority: "high", sentiment: "negative" },
69
80
  }),
70
81
  });
71
82
 
@@ -73,6 +84,8 @@ const result = await ctx.ai.generate({ prompt: "summarizeTicket", input: { ticke
73
84
  expect(result.priority).toBe("high");
74
85
  ```
75
86
 
87
+ `mockAI` keys responses by **operation** (`generate`, `extract`, `classify`, `retrieve`), not by prompt name. All prompts share the configured response for each operation.
88
+
76
89
  ### `createTestContext`
77
90
 
78
91
  Build a test `ctx` with controllable mocks:
@@ -101,21 +114,21 @@ const execution = await simulateFlow(myFlow, input, {
101
114
  ## Security Test Patterns
102
115
 
103
116
  ```ts
104
- // Assert unauthorized access is rejected
105
- await expect(
106
- runCapability("deleteUser", {
107
- input: { userId: "usr_1" },
108
- auth: { roles: ["viewer"], scopes: [], provider: "test" },
109
- })
110
- ).rejects.toMatchObject({ code: "forbidden" });
111
-
112
- // Assert cross-tenant access is blocked
113
- await expect(
114
- runCapability("getUser", {
115
- input: { userId: "usr_1" },
116
- auth: { tenantId: "other_tenant", roles: ["admin"], scopes: [], provider: "test" },
117
- })
118
- ).rejects.toMatchObject({ code: "forbidden" });
117
+ import { assertCapabilityDenied } from "@plumbus/core/testing";
118
+
119
+ // Assert unauthorized access is rejected (returns { success: false }, does not throw)
120
+ const denied = await runCapability(
121
+ deleteUser,
122
+ { userId: "usr_1" },
123
+ { auth: { roles: ["viewer"], scopes: [], provider: "test" } },
124
+ );
125
+ expect(denied.success).toBe(false);
126
+ expect(denied.error?.code).toBe("forbidden");
127
+
128
+ // Or use the helper
129
+ await assertCapabilityDenied(deleteUser, { userId: "usr_1" }, {
130
+ auth: { roles: ["viewer"], scopes: [], provider: "test" },
131
+ });
119
132
  ```
120
133
 
121
134
  ## Governance Test Patterns
@@ -75,11 +75,11 @@ The locale is resolved from the request context.
75
75
  | `plumbus translation new <name> --locale-folders` | Scaffold `en/`, `he/` message files + thin assembler |
76
76
  | `plumbus translation export` | Export to JSON or XLIFF 2.0 for professional translation |
77
77
  | `plumbus translation import` | Import translated JSON/XLIFF files back into source |
78
- | `plumbus translation status` | Report per-locale completion percentage |
78
+ | `plumbus translation status` | Report per-locale completion percentage (exits non-zero when incomplete) |
79
79
 
80
80
  ## Frontend
81
81
 
82
- When translations exist, `plumbus ui generate` produces `{frontend}/i18n/` with next-intl integration.
82
+ When translations exist, `plumbus ui generate` produces `{frontend}/i18n/` with next-intl integration (messages, config, typed keys, AppConfig, provider, request). Generate fails closed on incomplete locale coverage unless you pass `--skip-locale-parity`.
83
83
 
84
84
  Optional: emit per-locale bundles (same runtime API, smaller generated files):
85
85
 
@@ -90,7 +90,7 @@ plumbus ui generate --out-dir frontend --split-locale-bundles
90
90
  Without the flag, output is unchanged — a single `i18n/messages.ts`.
91
91
 
92
92
  ```tsx
93
- import { useTranslations } from "next-intl";
93
+ import { useTranslations } from "../i18n";
94
94
 
95
95
  function Nav() {
96
96
  const t = useTranslations("common");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumbus/core",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Plumbus framework core — types, SDK, runtime, CLI, test utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,7 +32,8 @@
32
32
  },
33
33
  "files": [
34
34
  "dist",
35
- "instructions"
35
+ "instructions",
36
+ "CHANGELOG.md"
36
37
  ],
37
38
  "keywords": [
38
39
  "plumbus",
@@ -94,9 +95,9 @@
94
95
  "@vitest/browser": "^3.0.0",
95
96
  "esbuild": "^0.25.12",
96
97
  "typescript": "^5.7.0",
97
- "@plumbus/api": "0.1.2",
98
98
  "@plumbus/mcp": "0.5.1",
99
- "@plumbus/voice": "0.3.0"
99
+ "@plumbus/voice": "0.3.0",
100
+ "@plumbus/api": "0.1.2"
100
101
  },
101
102
  "scripts": {
102
103
  "build": "tsc -b",