@voltro/cli 0.22.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +427 -0
  2. package/bin/voltro.mjs +39 -4
  3. package/dist/{apiBuild-DiWxVz-M.js → apiBuild-B2m4XK_8.js} +45 -23
  4. package/dist/apiBuild-UEM3QBke.js +2 -0
  5. package/dist/bin.js +3 -3
  6. package/dist/{commands-BxRaIOBG.js → commands-CaIhTsC2.js} +2944 -2557
  7. package/dist/{dbCommand-DVASmZj2.js → dbCommand-CC61CsAc.js} +474 -263
  8. package/dist/dbCommand-Cr__4ATv.js +2 -0
  9. package/dist/{dev-Dd3EZzj5.js → dev-DPkQVUTP.js} +1 -1
  10. package/dist/{dev-BRiPgbKw.js → dev-NCuEhNxs.js} +1593 -1575
  11. package/dist/index.js +1 -1
  12. package/dist/{inspectMetrics-9ZSuDeqD.js → inspectMetrics-dcC7gnxu.js} +566 -552
  13. package/dist/{serveCommand-DXJOARkO.js → serveCommand-C4gzDZzc.js} +425 -411
  14. package/dist/serveEntry.js +2 -2
  15. package/dist/{start-CsIjcOi-.js → start-C_4VUDT8.js} +327 -325
  16. package/dist/startEntry.js +2 -2
  17. package/package.json +17 -17
  18. package/templates/AGENTS.md +1 -1
  19. package/templates/agent-docs/_index.md +1 -1
  20. package/templates/agent-docs/_manifest.json +1 -1
  21. package/templates/agent-docs/authentication.md +21 -0
  22. package/templates/agent-docs/caching.md +21 -5
  23. package/templates/agent-docs/cli.md +61 -0
  24. package/templates/agent-docs/data.md +208 -170
  25. package/templates/agent-docs/database/migrations.md +123 -0
  26. package/templates/agent-docs/database/seedsdialects.md +12 -0
  27. package/templates/agent-docs/deployment.md +66 -1
  28. package/templates/agent-docs/plugins.md +42 -0
  29. package/templates/agent-docs/routing.md +23 -1
  30. package/templates/agent-docs/templates/apibackends.md +2 -2
  31. package/templates/agent-docs/whats-new.md +281 -32
  32. package/templates/agent-docs/workflows.md +30 -0
  33. package/templates/apps/api-ai/package.json +8 -7
  34. package/templates/apps/api-auth/package.json +9 -8
  35. package/templates/apps/api-backend/package.json +8 -7
  36. package/templates/apps/api-backend-deactivation/package.json +8 -7
  37. package/templates/apps/api-backend-mail/package.json +9 -8
  38. package/templates/apps/api-backend-mariadb/package.json +10 -9
  39. package/templates/apps/api-backend-storage/package.json +9 -8
  40. package/templates/apps/api-data-advanced/package.json +9 -8
  41. package/templates/apps/api-durable/package.json +9 -8
  42. package/templates/apps/api-feature-flags/package.json +10 -9
  43. package/templates/apps/api-governance/package.json +9 -8
  44. package/templates/apps/api-kv/package.json +9 -8
  45. package/templates/apps/api-moderation/package.json +9 -8
  46. package/templates/apps/api-observability/package.json +9 -8
  47. package/templates/apps/api-ratelimit/package.json +9 -8
  48. package/templates/apps/api-rbac/package.json +9 -8
  49. package/templates/apps/api-rest/package.json +8 -7
  50. package/templates/apps/api-saas/package.json +12 -11
  51. package/templates/apps/api-search/package.json +9 -8
  52. package/templates/apps/api-versioning/app.config.ts +6 -4
  53. package/templates/apps/api-versioning/package.json +9 -8
  54. package/templates/apps/api-webhooks/package.json +10 -9
  55. package/templates/apps/changelog/package.json +7 -6
  56. package/templates/apps/edge-functions/package.json +3 -2
  57. package/templates/apps/frontend-admin/package.json +9 -8
  58. package/templates/apps/frontend-app/package.json +9 -8
  59. package/templates/apps/frontend-blank/package.json +8 -7
  60. package/templates/apps/frontend-contact/package.json +8 -7
  61. package/templates/apps/frontend-contact/src/globals.d.ts +6 -0
  62. package/templates/apps/frontend-dashboard/package.json +8 -7
  63. package/templates/apps/frontend-docs/package.json +8 -7
  64. package/templates/apps/frontend-docs/src/globals.d.ts +6 -0
  65. package/templates/apps/frontend-i18n/package.json +7 -6
  66. package/templates/apps/frontend-landing/package.json +8 -7
  67. package/templates/apps/frontend-landing/src/globals.d.ts +6 -0
  68. package/templates/apps/frontend-spa/package.json +8 -7
  69. package/templates/apps/frontend-spa/src/globals.d.ts +6 -0
  70. package/templates/apps/frontend-ssr/package.json +8 -7
  71. package/templates/apps/frontend-ssr/src/globals.d.ts +6 -0
  72. package/templates/apps/frontend-ssr-api/package.json +9 -8
  73. package/templates/apps/frontend-static-blog/package.json +7 -6
  74. package/templates/apps/frontend-static-blog/src/globals.d.ts +6 -0
  75. package/dist/apiBuild-COyPDf3R.js +0 -2
  76. package/dist/dbCommand-CO3eSAZR.js +0 -2
@@ -1,3 +1,3 @@
1
- import { X as e } from "./inspectMetrics-9ZSuDeqD.js";
2
- import { t } from "./start-CsIjcOi-.js";
1
+ import { Z as e } from "./inspectMetrics-dcC7gnxu.js";
2
+ import { t } from "./start-C_4VUDT8.js";
3
3
  export { e as loadDotEnv, t as runStartCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/cli",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
4
4
  "description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -62,22 +62,22 @@
62
62
  "@effect/platform-node": "^0.108.0",
63
63
  "@effect/sql": "^0.52.0",
64
64
  "@effect/workflow": "^0.19.0",
65
- "@voltro/ai": "0.22.1",
66
- "@voltro/cache": "0.22.1",
67
- "@voltro/data-transfer": "0.22.1",
68
- "@voltro/database": "0.22.1",
69
- "@voltro/env": "0.22.1",
70
- "@voltro/kv": "0.22.1",
71
- "@voltro/logger": "0.22.1",
72
- "@voltro/plugin-auth": "0.22.1",
73
- "@voltro/plugin-broadcast": "0.22.1",
74
- "@voltro/plugin-mail": "0.22.1",
75
- "@voltro/plugin-storage": "0.22.1",
76
- "@voltro/plugin-webhooks": "0.22.1",
77
- "@voltro/protocol": "0.22.1",
78
- "@voltro/runtime": "0.22.1",
79
- "@voltro/serverless": "0.22.1",
80
- "@voltro/workflow": "0.22.1",
65
+ "@voltro/ai": "0.24.0",
66
+ "@voltro/cache": "0.24.0",
67
+ "@voltro/data-transfer": "0.24.0",
68
+ "@voltro/database": "0.24.0",
69
+ "@voltro/env": "0.24.0",
70
+ "@voltro/kv": "0.24.0",
71
+ "@voltro/logger": "0.24.0",
72
+ "@voltro/plugin-auth": "0.24.0",
73
+ "@voltro/plugin-broadcast": "0.24.0",
74
+ "@voltro/plugin-mail": "0.24.0",
75
+ "@voltro/plugin-storage": "0.24.0",
76
+ "@voltro/plugin-webhooks": "0.24.0",
77
+ "@voltro/protocol": "0.24.0",
78
+ "@voltro/runtime": "0.24.0",
79
+ "@voltro/serverless": "0.24.0",
80
+ "@voltro/workflow": "0.24.0",
81
81
  "chokidar": "^5.0.0",
82
82
  "ioredis": "^5.11.1",
83
83
  "tinyglobby": "^0.2.17",
@@ -595,7 +595,7 @@ each plugin's own README.
595
595
 
596
596
  | Topic | Open | Summary |
597
597
  |---|---|---|
598
- | **What's new in 0.22.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
598
+ | **What's new in 0.23.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
599
599
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
600
600
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
601
601
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -9,7 +9,7 @@ each plugin's own README.
9
9
 
10
10
  | Topic | Open | Summary |
11
11
  |---|---|---|
12
- | **What's new in 0.22.1** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
12
+ | **What's new in 0.23.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
13
13
  | AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
14
14
  | Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
15
15
  | Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
@@ -89,7 +89,7 @@
89
89
  "group": "database.migrations",
90
90
  "description": "Voltro's planner-based migration system — diff your declared schema against the live DB, classify each change, refuse-to-apply anything risky without explicit intent. Dev auto-applies, prod refuses.",
91
91
  "path": "agent-docs/database/migrations.md",
92
- "files": 16
92
+ "files": 17
93
93
  },
94
94
  {
95
95
  "id": "database/misc",
@@ -166,6 +166,27 @@ import { handleSignIn } from '@voltro/plugin-auth'
166
166
 
167
167
  Use `handleSignIn` instead of rolling your own — the timing-oracle gap is the kind of subtle bug that hides for years. Note it returns a `401 HandlerResult`, it does not throw a domain error.
168
168
 
169
+ ## Brute-force lockout
170
+
171
+ `plugin-auth` locks an account after repeated failed credential attempts, so password-spraying and credential-stuffing don't get unlimited guesses. After **5 failed attempts** (a wrong password — or, for MFA users, a wrong second-factor code) within **15 minutes**, sign-in for that email is refused with a `429 account_locked` (carrying a `retryAfterSeconds`) for **15 minutes**. A completed login clears the counter.
172
+
173
+ The counter is keyed by **email**, not user id, and it tracks unknown addresses too: a locked account and an unknown-but-hammered address respond identically, so the lock can't be turned into an existence oracle — the same reasoning as the timing-oracle defence above.
174
+
175
+ It is **on by default** — a security default you get for free. Tune or disable it per app:
176
+
177
+ ```ts
178
+ authRoutesPlugin({
179
+ store,
180
+ lockout: {
181
+ maxAttempts: 5, // failed attempts before locking (default 5)
182
+ windowSeconds: 900, // counting window (default 15 min)
183
+ lockSeconds: 900, // lock duration (default 15 min)
184
+ },
185
+ })
186
+ ```
187
+
188
+ The counter lives in the `loginAttempts` table (contributed via `authTables`), so it appears automatically on your next `voltro db apply` / `voltro dev` boot. For lockout that holds across multiple nodes, back the store with Postgres (`postgresUserStore`) — the in-memory store is single-node.
189
+
169
190
  ## Rehashing on parameter bump
170
191
 
171
192
  When the framework updates the default cost parameters, existing hashes stay valid — `verifyPassword` reads `N`/`r`/`p` from the stored hash string itself (they're encoded inline as `scrypt$<N>$<r>$<p>$…`). Rehash-on-verify ships: `needsRehash(stored)` reports whether a hash is below the current cost, and `verifyPasswordWithRehash(plaintext, stored)` returns `{ valid, rehash? }` — when the password matches an under-cost hash, `rehash` is a freshly-minted replacement. `handleSignIn` wires this through `UserStore.updatePassword`, so a user's stored hash silently strengthens on their next login, no forced reset and no backfill.
@@ -286,12 +286,13 @@ The matching `.<primitive>.server.ts` is **unchanged** — caching is a descript
286
286
 
287
287
  ## `scope` is required — and it's a security decision
288
288
 
289
- `scope` has **no default**, because guessing wrong leaks one user's rows to another. The rubric is one question:
289
+ `scope` has **no default**, because guessing wrong leaks one user's rows to another. The rubric is one question, with **three** answers:
290
290
 
291
- > **Does the resolved query depend on the caller?**
291
+ > **Does the resolved query depend on the caller — and on what about them?**
292
292
 
293
- - **Yes** — tenant-filtered, `where authorId = me`, anything row-scoped → **`scope: 'subject'`**. The cache key includes the caller's subject id, so two subjects can never share an entry.
294
- - **No** — the same rows for everyone (reference / lookup data) → **`scope: 'global'`**. One entry shared across all callers.
293
+ - **On the PERSON** — `where authorId = me`, anything row-scoped → **`scope: 'subject'`**. The cache key includes the caller's subject id, so two subjects can never share an entry.
294
+ - **On their ORG only** — an org-wide figure every colleague sees identically → **`scope: 'tenant'`**. One entry per `tenantId`, never shared across orgs.
295
+ - **On neither** — the same rows for everyone (reference / lookup data) → **`scope: 'global'`**. One entry shared across all callers.
295
296
 
296
297
  ```ts
297
298
  // reference data — identical for everyone → global
@@ -302,9 +303,24 @@ export const listCountries = defineQuery({
302
303
  output: Country,
303
304
  cache: { ttl: '1h', scope: 'global' },
304
305
  })
306
+
307
+ // an org-wide statistic — same for all 18 colleagues, never across orgs
308
+ export const last12Months = defineQuery({
309
+ name: 'globalStatistics.last12Months',
310
+ source: ['invoices', 'employees'],
311
+ input: Schema.Struct({}),
312
+ output: Stats,
313
+ cache: { ttl: '5m', scope: 'tenant' },
314
+ })
305
315
  ```
306
316
 
307
- **Never put `scope: 'global'` on a subject-filtered query.** Tenant tables are auto-scoped per subject by the runtime, so a `global` cache over one would serve tenant A's rows to tenant B. When in doubt, use `subject`.
317
+ `'tenant'` exists because the other two were the only options and neither fit an org-wide figure: `'subject'` recomputes it per person eighteen identical computations of the same nine-table statistic for an eighteen-person org and `'global'` shares one entry across tenant boundaries, which for data derived from `subject.tenantId` is not a cache but a leak.
318
+
319
+ A caller with no `tenantId` (an anonymous or system subject) **bypasses** a `'tenant'` cache rather than sharing a null-keyed entry.
320
+
321
+ **Never put `scope: 'global'` on a subject- or tenant-filtered query.** Tenant tables are auto-scoped by the runtime, so a `global` cache over one would serve tenant A's rows to tenant B. **The boot audit checks this**: a `'global'` scope over a `tenant()`-scoped table is reported by `voltro dev` and refused under `VOLTRO_SERVER_ONLY=strict`. It stays silent for `'global'` on reference data — the case the option exists for — and for a query with no declared `source`, where it has nothing to reason about.
322
+
323
+ **`scope: 'tenant'` is not a replacement for modelling.** For a rollup, an aggregate with `tenantId` as an indexed column puts the tenant boundary in the *data* rather than in a cache key, which is better. `'tenant'` is for the other case: a query that must be FRESH and is merely expensive, where an aggregate's refresh interval is the wrong instrument.
308
324
 
309
325
  ## How auto-invalidation works
310
326
 
@@ -1069,6 +1069,21 @@ source counts as a guard, so `requireTeamAccess()` is recognised without any
1069
1069
  configuration. Without that the scan would report every call site of your own
1070
1070
  guards, which is the failure mode that makes a check ignorable.
1071
1071
 
1072
+ It reads your **whole source tree** for those names, not just the
1073
+ convention-named files — guards live in `lib/access.ts`, not in `*.mutation.ts`.
1074
+ The line above the counts tells you what it found, and it is worth reading before
1075
+ you trust the numbers:
1076
+
1077
+ ```txt
1078
+ guard vocabulary: 17 from your source (requireTeamAccess, assertInquiryAccess, …)
1079
+ ```
1080
+
1081
+ If it instead says `framework names only — no exported require*/assert* found in
1082
+ this app` while you know you export some, the counts below it are not meaningful:
1083
+ every call site of your own guards is being reported as unguarded. Check that
1084
+ they are `export`ed and that the name starts with `require` / `assert` followed
1085
+ by a capital.
1086
+
1072
1087
  **An inline ownership check is informational.** `row.userId !== subject.id → new
1073
1088
  AccessDeniedError({})` is correct code — it is listed so you can see where the
1074
1089
  rule lives in a handler rather than on a descriptor, and it never fails the run.
@@ -2159,6 +2174,23 @@ voltro data restore ./backups/2026-07-01 # pg_restore / mysql / …
2159
2174
 
2160
2175
  These shell out to the vendor tools resolved from your `DB_DIALECT` + connection env. They produce a dialect-native artifact (`db.dump`, `db.sql`, `db.sqlite`, `db.bacpac`) that is lossless and point-in-time consistent for **same-dialect** restore — the right tool for disaster recovery. Secrets are passed via the tools' environment variables (`PGPASSWORD`, `MYSQL_PWD`), never on the command line, where the tool supports it. The named tool must be installed and on `PATH`.
2161
2176
 
2177
+ ### The provenance stamp — a restore that refuses the wrong DB
2178
+
2179
+ A native dump is opaque: it doesn't say which dialect made it, which schema shape it carries, or when. `backup` writes a sidecar `voltro-backup-stamp.json` next to the artifact recording exactly that — `dialect`, the live schema `fingerprint`, the `@voltro/cli` version, and the timestamp.
2180
+
2181
+ `restore` reads the stamp **before touching the DB** and acts on two failures that are otherwise silent until they corrupt:
2182
+
2183
+ - **Cross-dialect restore → refused.** Restoring a postgres dump while `DB_DIALECT=mysql` is never valid; it stops with an error instead of half-loading. Override with `--force` only if you genuinely know better.
2184
+ - **Schema/code skew → warned.** If the backup's schema fingerprint differs from what the running code declares, restore prints a warning to run `voltro db apply` afterwards — the dump's shape predates (or postdates) this deploy's code. (Production boot already refuses on a fingerprint mismatch; the stamp surfaces it at restore time, before the boot.)
2185
+
2186
+ A backup with no stamp (older, or hand-made) restores with a caution rather than a hard stop.
2187
+
2188
+ ### Point-in-time recovery (PITR) is your database's job, not the framework's
2189
+
2190
+ `backup` is a point-in-time **snapshot**. "Restore to 14:32, just before the bad deploy" (PITR) needs continuous WAL/binlog archiving, which lives at the database/provider layer — pg's `archive_command` + a base backup (pgBackRest / WAL-G), a managed provider's continuous backup (RDS, Cloud SQL, Neon, PlanetScale). The framework deliberately does **not** reimplement it: layer PITR under these native snapshots at the infra layer. A weekly `voltro data backup` + provider PITR together give you both a portable artifact and a fine-grained restore point.
2191
+
2192
+ > **Test your backups.** A backup you've never restored is a hypothesis. Restore your latest artifact into a throwaway database and boot the app against it on a schedule — the stamp's dialect/fingerprint checks turn a silently-broken backup into a loud one, but only an actual restore proves the bytes are good.
2193
+
2162
2194
  ## Masking (prod → dev/stage safely)
2163
2195
 
2164
2196
  Cloning prod into a lower environment must not carry real user data. `voltro data export`
@@ -2606,6 +2638,35 @@ voltro update --codemods-only --from 0.5.0 --to 0.6.0
2606
2638
 
2607
2639
  Every `@voltro/*` entry in `dependencies` and `devDependencies` — in every workspace member, see above — with the range style preserved (`^0.3.0` stays caret, `~0.3.0` stays tilde) unless you pass `--exact`. Non-registry specs (`workspace:*`, `catalog:`, `link:`, …) are left untouched — they're already resolved by your monorepo or catalog.
2608
2640
 
2641
+ ### And the peer dependencies the framework requires
2642
+
2643
+ `@effect/*` are **peer** dependencies, so your app declares them directly. When a
2644
+ release moves its peer range, bumping only `@voltro/*` leaves you installed
2645
+ against the old ones:
2646
+
2647
+ ```txt
2648
+ Aligning peer dependencies the framework requires:
2649
+ @effect/rpc ^0.75.1 → ^0.76.0 (apps/api/package.json)
2650
+ @effect/platform ^0.96.2 → ^0.97.0 (apps/api/package.json)
2651
+ ```
2652
+
2653
+ `update` reads those requirements off the freshly installed `@voltro/*` packages
2654
+ and re-installs if anything moved. Without it your package manager only *warns*,
2655
+ and the app compiles and boots on a graph the framework was never tested against
2656
+ — which is the failure mode with no symptom until there is one.
2657
+
2658
+ It is deliberately conservative:
2659
+
2660
+ - **Only peers you already declare.** One resolved transitively is not `update`'s
2661
+ to add.
2662
+ - **Only when your range is genuinely lower.** Pinned ahead, or pinned exactly at
2663
+ the floor (`0.76.0` vs `^0.76.0`), is left alone — that is a choice.
2664
+ - **Only ranges it can judge** (`^`, `~`, `>=`, exact). A union (`^1 || ^2`), a
2665
+ bounded range, `workspace:` / `catalog:` — untouched.
2666
+
2667
+ If two framework packages disagree about one peer, it says so and changes
2668
+ nothing: that is our bug, not yours to absorb silently.
2669
+
2609
2670
  ## When the install cannot run on this host
2610
2671
 
2611
2672
  Some projects install in a container with their own store, from an offline