@voltro/cli 0.35.0 → 0.37.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.
- package/CHANGELOG.md +158 -0
- package/dist/apiBuild-D0f6hYbH.js +2 -0
- package/dist/{apiBuild-DYD_ONLD.js → apiBuild-DGsShRuF.js} +1 -1
- package/dist/{baselineCommand-DIttzO8A.js → baselineCommand-C6NMt-oa.js} +1 -4
- package/dist/baselineCommand-Cfw2Afwm.js +2 -0
- package/dist/bin.js +1 -1
- package/dist/{build-CD8K4XOr.js → build-CiFA-E19.js} +7 -7
- package/dist/{checkCommand-DKpDLlqu.js → checkCommand-CIPYyNyc.js} +1 -1
- package/dist/{checkCommand-Ct9xkTrS.js → checkCommand-CLPTzYqM.js} +3 -3
- package/dist/{cloudCmd-Cvv5HGaZ.js → cloudCmd-NQSwe_Qk.js} +1 -1
- package/dist/{codegen-CYM3Zqrf.js → codegen-GYYcdpCg.js} +2 -2
- package/dist/codegen-uYrrDzQv.js +2 -0
- package/dist/{codegenCommand-C4YoQIc2.js → codegenCommand-BxS3sCeJ.js} +2 -2
- package/dist/{codemodRunner-BnFq3Fgu.js → codemodRunner-cyqu5oxr.js} +122 -46
- package/dist/{commands-BE8E7zF3.js → commands-BwG9A9Vv.js} +73 -72
- package/dist/{dashboardCommand-D7SgZGaN.js → dashboardCommand-C7U146xQ.js} +1 -1
- package/dist/{dataCommand-BhYwDgg-.js → dataCommand-BOwd8_aJ.js} +2 -2
- package/dist/{dbCommand-O8HA63s2.js → dbCommand-C4UW8cVN.js} +353 -351
- package/dist/dbCommand-CC-qNzpz.js +2 -0
- package/dist/{dev-D2BikO7a.js → dev-BV6XNYKf.js} +1 -1
- package/dist/{dev-C7sFZq3m.js → dev-bwDgSObH.js} +2000 -2000
- package/dist/{doctorCommand-DnimF5IM.js → doctorCommand-BXyrtN2l.js} +9 -9
- package/dist/doctorCommand-C4L61G_L.js +2 -0
- package/dist/{dormancyCommand-QewYug_s.js → dormancyCommand-BDdRRLsa.js} +1 -1
- package/dist/{embeddingsCommand-BfiLS_QI.js → embeddingsCommand-CjWGcAE6.js} +1 -1
- package/dist/{envCommand-CCGPRQY1.js → envCommand-B_TeG297.js} +1 -1
- package/dist/{evolveCommand-CHsLCtDf.js → evolveCommand-D6N-WfA4.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{infoCommand-DwOgK1t6.js → infoCommand-BHKTprrx.js} +1 -1
- package/dist/inspectGateHint-BF6608UT.js +4 -0
- package/dist/{metaCommands-7MJfZ5cf.js → metaCommands-CIoQNlaQ.js} +1 -1
- package/dist/{migrate-BV7I-ZHZ.js → migrate-RwgUWXfc.js} +1 -1
- package/dist/{privacyCommand-C-Df56U_.js → privacyCommand-XejDMvmu.js} +4 -3
- package/dist/{probeCommand-CZfaaUOZ.js → probeCommand-BPEpwT32.js} +40 -39
- package/dist/{projectScaffold-DmzEKHib.js → projectScaffold-CxgtJvlb.js} +1 -1
- package/dist/{projectScaffold-LMMtaavR.js → projectScaffold-DDgWbNLe.js} +2 -2
- package/dist/{runtimeTrace-CH3eUiMw.js → runtimeTrace-WYV7qYvz.js} +1 -1
- package/dist/{sdkgen-BLkvGRfX.js → sdkgen-TM5sSdf7.js} +1 -1
- package/dist/{serveCommand-iwlUBNS1.js → serveCommand-CyZsXL1l.js} +487 -487
- package/dist/serveCommand-qumm7jdF.js +2 -0
- package/dist/serveEntry.js +1 -1
- package/dist/subcommandNames-CKG5Dz3a.js +27 -0
- package/dist/{templates-De8IR5-c.js → templates-BR-fb4SP.js} +1 -4
- package/dist/{test-CI6iDsYc.js → test-DIQ0jlkQ.js} +1 -1
- package/dist/{updateCommand-x0pI_x-B.js → updateCommand-9GHHsmDf.js} +1 -1
- package/dist/updateCommand-BN0tkl9-.js +2 -0
- package/dist/{webhooksCommand-DlAgS2Iw.js → webhooksCommand-CHWSPk5z.js} +1 -1
- package/package.json +29 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +38 -0
- package/templates/agent-docs/data.md +30 -0
- package/templates/agent-docs/database/misc.md +33 -0
- package/templates/agent-docs/plugins/audit.md +46 -0
- package/templates/agent-docs/whats-new.md +44 -121
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +3 -3
- package/dist/apiBuild-B8aoJvuw.js +0 -2
- package/dist/baselineCommand-C2ClWZN3.js +0 -2
- package/dist/codegen-ChBi_hVa.js +0 -2
- package/dist/dbCommand-DS4b97Is.js +0 -2
- package/dist/doctorCommand-CM4Ch9C7.js +0 -2
- package/dist/serveCommand-CbDHU6l-.js +0 -2
- package/dist/updateCommand-BlMZhWgO.js +0 -2
package/dist/serveEntry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as e } from "./loadEnv-D9nEOClM.js";
|
|
2
2
|
import { r as t } from "./appModuleLoader-C9r9mxZt.js";
|
|
3
3
|
import { i as n } from "./dialectDriver-CgXnDfec.js";
|
|
4
|
-
import { t as r } from "./serveCommand-
|
|
4
|
+
import { t as r } from "./serveCommand-CyZsXL1l.js";
|
|
5
5
|
export { e as loadDotEnv, t as registerAppModules, n as registerDriver, r as runServe };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/subcommandNames.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"plan",
|
|
4
|
+
"apply",
|
|
5
|
+
"branch",
|
|
6
|
+
"plans",
|
|
7
|
+
"drift",
|
|
8
|
+
"squash",
|
|
9
|
+
"restore-snapshot",
|
|
10
|
+
"generate",
|
|
11
|
+
"migrate",
|
|
12
|
+
"rollback",
|
|
13
|
+
"status",
|
|
14
|
+
"files",
|
|
15
|
+
"rollback-file",
|
|
16
|
+
"seed",
|
|
17
|
+
"gc-snapshots",
|
|
18
|
+
"adopt",
|
|
19
|
+
"scan-credentials",
|
|
20
|
+
"encrypt-column"
|
|
21
|
+
], t = [
|
|
22
|
+
"scope",
|
|
23
|
+
"export",
|
|
24
|
+
"erase"
|
|
25
|
+
], n = (e) => `<${e.join("|")}>`;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { t as n, n as r, e as t };
|
|
@@ -4,10 +4,7 @@ import { fileURLToPath as s } from "node:url";
|
|
|
4
4
|
//#region src/templates.ts
|
|
5
5
|
var c = e(s(import.meta.url)), l = (() => {
|
|
6
6
|
let n = process.env.VOLTRO_TEMPLATES_DIR;
|
|
7
|
-
if (n)
|
|
8
|
-
let e = t(n, "apps");
|
|
9
|
-
if (i(e)) return e;
|
|
10
|
-
}
|
|
7
|
+
if (n) return t(n, "apps");
|
|
11
8
|
let a = r(c, "..", "templates", "apps"), o = c;
|
|
12
9
|
for (let n = 0; n < 12; n++) {
|
|
13
10
|
for (let e of ["voltro-templates", ".voltro-templates"]) {
|
|
@@ -79,7 +79,7 @@ var d = l({ scope: "voltro:test" }), f = (e) => {
|
|
|
79
79
|
}, C = async (e) => {
|
|
80
80
|
let t = m(e);
|
|
81
81
|
if (!o(t.root)) return console.error(`voltro test: no such directory: ${t.root}`), 1;
|
|
82
|
-
let { checkRpcGroupFreshness: n } = await import("./codegen-
|
|
82
|
+
let { checkRpcGroupFreshness: n } = await import("./codegen-uYrrDzQv.js"), r = await n(t.root);
|
|
83
83
|
if (r.kind === "stale") return console.error(`voltro test: ${r.file} is stale — ${r.reason}.`), console.error(" The tests would run against the previously generated procedure group, pass, and prove nothing about the descriptors you just edited.\n Run `voltro codegen` (or boot `voltro dev` once) and try again."), 1;
|
|
84
84
|
d.info("running vitest", {
|
|
85
85
|
root: t.root,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as e, r as t, t as n } from "./codemodRunner-
|
|
1
|
+
import { n as e, r as t, t as n } from "./codemodRunner-cyqu5oxr.js";
|
|
2
2
|
import { basename as r, dirname as i, join as a, relative as o, resolve as s } from "node:path";
|
|
3
3
|
import { existsSync as c, readFileSync as l, readdirSync as u, statSync as d, writeFileSync as f } from "node:fs";
|
|
4
4
|
import { totalmem as p } from "node:os";
|
|
@@ -222,7 +222,7 @@ createVerifier({ secret: [process.env.WEBHOOK_SECRET, process.env.WEBHOOK_SECRET
|
|
|
222
222
|
...t === void 0 ? {} : { payload: t }
|
|
223
223
|
};
|
|
224
224
|
}, S = u({ scope: "voltro:webhooks" }), C = ["--out", "--name"], w = async (e) => {
|
|
225
|
-
let { walk: t, loadDiscovered: n } = await import("./dev-
|
|
225
|
+
let { walk: t, loadDiscovered: n } = await import("./dev-BV6XNYKf.js"), { outgoingFromEvents: r } = await import("./webhookDiscovery-CrGAfhIG.js");
|
|
226
226
|
return r((await n(await t(e))).events.map((e) => ({
|
|
227
227
|
file: e.file,
|
|
228
228
|
descriptor: e.descriptor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.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",
|
|
@@ -568,6 +568,18 @@
|
|
|
568
568
|
"title": "The declared schema no longer reads `CDC` / `VOLTRO_UNDO` / `VOLTRO_TRACING_PERSIST`",
|
|
569
569
|
"kind": "manual"
|
|
570
570
|
},
|
|
571
|
+
{
|
|
572
|
+
"version": "0.35.1",
|
|
573
|
+
"id": "0.35.1/01_unauthorized-replaces-session-expired",
|
|
574
|
+
"title": "A 401 is `code: 'unauthorized'`; the vault's own failure is `'credential_unusable'`",
|
|
575
|
+
"kind": "transform"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"version": "0.37.0",
|
|
579
|
+
"id": "0.37.0/01_procedure-input-rejects-undeclared-fields",
|
|
580
|
+
"title": "a procedure input rejects a field it does not declare (it used to drop it)",
|
|
581
|
+
"kind": "manual"
|
|
582
|
+
},
|
|
571
583
|
{
|
|
572
584
|
"version": "0.4.0",
|
|
573
585
|
"id": "0.4.0/01_rbac-forbidden-to-scopeerror",
|
|
@@ -667,22 +679,22 @@
|
|
|
667
679
|
"@effect/platform-node": "^0.108.0",
|
|
668
680
|
"@effect/sql": "^0.52.0",
|
|
669
681
|
"@effect/workflow": "^0.19.0",
|
|
670
|
-
"@voltro/ai": "0.
|
|
671
|
-
"@voltro/cache": "0.
|
|
672
|
-
"@voltro/data-transfer": "0.
|
|
673
|
-
"@voltro/database": "0.
|
|
674
|
-
"@voltro/env": "0.
|
|
675
|
-
"@voltro/kv": "0.
|
|
676
|
-
"@voltro/logger": "0.
|
|
677
|
-
"@voltro/plugin-auth": "0.
|
|
678
|
-
"@voltro/plugin-broadcast": "0.
|
|
679
|
-
"@voltro/plugin-mail": "0.
|
|
680
|
-
"@voltro/plugin-storage": "0.
|
|
681
|
-
"@voltro/plugin-webhooks": "0.
|
|
682
|
-
"@voltro/protocol": "0.
|
|
683
|
-
"@voltro/runtime": "0.
|
|
684
|
-
"@voltro/serverless": "0.
|
|
685
|
-
"@voltro/workflow": "0.
|
|
682
|
+
"@voltro/ai": "0.37.0",
|
|
683
|
+
"@voltro/cache": "0.37.0",
|
|
684
|
+
"@voltro/data-transfer": "0.37.0",
|
|
685
|
+
"@voltro/database": "0.37.0",
|
|
686
|
+
"@voltro/env": "0.37.0",
|
|
687
|
+
"@voltro/kv": "0.37.0",
|
|
688
|
+
"@voltro/logger": "0.37.0",
|
|
689
|
+
"@voltro/plugin-auth": "0.37.0",
|
|
690
|
+
"@voltro/plugin-broadcast": "0.37.0",
|
|
691
|
+
"@voltro/plugin-mail": "0.37.0",
|
|
692
|
+
"@voltro/plugin-storage": "0.37.0",
|
|
693
|
+
"@voltro/plugin-webhooks": "0.37.0",
|
|
694
|
+
"@voltro/protocol": "0.37.0",
|
|
695
|
+
"@voltro/runtime": "0.37.0",
|
|
696
|
+
"@voltro/serverless": "0.37.0",
|
|
697
|
+
"@voltro/workflow": "0.37.0",
|
|
686
698
|
"chokidar": "^5.0.0",
|
|
687
699
|
"ioredis": "^5.11.1",
|
|
688
700
|
"tinyglobby": "^0.2.17",
|
package/templates/AGENTS.md
CHANGED
|
@@ -707,7 +707,7 @@ each plugin's own README.
|
|
|
707
707
|
|
|
708
708
|
| Topic | Open | Summary |
|
|
709
709
|
|---|---|---|
|
|
710
|
-
| **What's new in 0.
|
|
710
|
+
| **What's new in 0.37.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. |
|
|
711
711
|
| 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. |
|
|
712
712
|
| 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. |
|
|
713
713
|
| 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.
|
|
12
|
+
| **What's new in 0.37.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. |
|
|
@@ -173,6 +173,22 @@ voltro db apply # execute it (dev)
|
|
|
173
173
|
# `voltro migrate` is an alias of `db apply` — same differ, shorter name (CI / ops)
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
### "I want to encrypt a column that already has rows in it"
|
|
177
|
+
|
|
178
|
+
Turning on `.encrypted()` needs two things: the cipher, and a migration of the
|
|
179
|
+
rows that are already there. The second has a command — it is easy to miss,
|
|
180
|
+
because nothing about a schema change suggests a data pass is owed.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
voltro db scan-credentials # find plaintext secrets, column by column
|
|
184
|
+
voltro db encrypt-column users.apiToken --dry-run
|
|
185
|
+
voltro db encrypt-column users.apiToken --yes
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`--key-env` names the key variable if it is not the default; the key must be the
|
|
189
|
+
SAME one the app runs with, or the rows come back undecryptable. `voltro db`
|
|
190
|
+
with no subcommand prints the full list.
|
|
191
|
+
|
|
176
192
|
### "I want a clean rebuild"
|
|
177
193
|
|
|
178
194
|
```bash
|
|
@@ -3677,6 +3693,28 @@ Codemods that span multiple versions run in order (e.g. upgrading `0.2.0 → 0.4
|
|
|
3677
3693
|
|
|
3678
3694
|
Use `voltro db apply` (the declarative diff), not `voltro db migrate` (the imperative file-runner) — only the former reconciles framework tables. If your app also ships `migrations/*.migration.ts`, `voltro db migrate .` runs both halves and is what refusal 4 above asks your pre-deploy job for.
|
|
3679
3695
|
|
|
3696
|
+
## Restart every process — a running one keeps the OLD modules
|
|
3697
|
+
|
|
3698
|
+
`voltro update` changes what is on disk. A process that was already running when
|
|
3699
|
+
you ran it keeps the module graph it loaded at boot, so it goes on executing the
|
|
3700
|
+
previous version indefinitely — and against a `.framework` directory that has
|
|
3701
|
+
since been rewritten.
|
|
3702
|
+
|
|
3703
|
+
That mix is worse than either version alone. A consumer lost half an hour to a
|
|
3704
|
+
pod whose api had started before the upgrade: it served requests, reported
|
|
3705
|
+
healthy, and returned no SSR at all, because the running process held the old
|
|
3706
|
+
modules while the build output on disk was new.
|
|
3707
|
+
|
|
3708
|
+
Restart every process after an update, including ones you did not deploy:
|
|
3709
|
+
|
|
3710
|
+
```sh
|
|
3711
|
+
kubectl rollout restart deploy/api deploy/web # or: docker compose up -d --force-recreate
|
|
3712
|
+
```
|
|
3713
|
+
|
|
3714
|
+
`voltro dev` reloads itself, so a development machine is not affected. Anything
|
|
3715
|
+
long-running is — a `voltro serve` / `voltro start` container, a worker, a
|
|
3716
|
+
process a supervisor kept alive across the upgrade.
|
|
3717
|
+
|
|
3680
3718
|
## After the update — the checklist
|
|
3681
3719
|
|
|
3682
3720
|
```bash
|
|
@@ -263,6 +263,36 @@ const { data } = useSubscription('app', 'messages.list', {
|
|
|
263
263
|
})
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
### An undeclared field is refused, not dropped
|
|
267
|
+
|
|
268
|
+
The input schema is the whole accepted key set. A field it does not declare
|
|
269
|
+
fails the call with a `ParseError` naming the key and listing what was expected:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
{ readonly channelId: string; readonly limit: number }
|
|
273
|
+
└─ ["employeeId"]
|
|
274
|
+
└─ is unexpected, expected: "channelId" | "limit"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
It used to be discarded silently, and the reason that is worse than it sounds is
|
|
278
|
+
what a discarded FILTER means. A consumer's layout called a `userSettings.list`
|
|
279
|
+
that declares `userId` with `{ employeeId }`; the payload decoded to `{}`, which
|
|
280
|
+
for a list query is not a narrower filter but the absence of one, and an admin
|
|
281
|
+
was served another user's row.
|
|
282
|
+
|
|
283
|
+
The decoder cannot tell a projection field from a filter field, so it refuses
|
|
284
|
+
either way. Three consequences worth knowing:
|
|
285
|
+
|
|
286
|
+
- Nested objects and union members follow the same rule — a stray key inside
|
|
287
|
+
`{ page: { limit, offset } }` is refused too.
|
|
288
|
+
- `Schema.Struct({})` means "this procedure takes nothing", and a call carrying
|
|
289
|
+
anything is refused. `Schema.Record(...)` keeps its open key set: there the
|
|
290
|
+
openness is declared.
|
|
291
|
+
- If a call site legitimately holds more than the procedure declares — a spread
|
|
292
|
+
of a wider filter object — narrow it at the call site rather than widening the
|
|
293
|
+
schema. Widening restores the silent drop under a different name: the field is
|
|
294
|
+
accepted and still does nothing.
|
|
295
|
+
|
|
266
296
|
## Descriptor-return vs computed-return
|
|
267
297
|
|
|
268
298
|
A query executor can return either:
|
|
@@ -330,6 +330,39 @@ Precedence, exactly:
|
|
|
330
330
|
3. **`.safe()`** on an encrypted column suppresses the implied secret and copies
|
|
331
331
|
it verbatim — say this only when you have genuinely reviewed the plaintext.
|
|
332
332
|
|
|
333
|
+
## Which stores decrypt — every one the framework hands you
|
|
334
|
+
|
|
335
|
+
The codec is applied by the store, so "which store" is the whole question. All
|
|
336
|
+
three of these decrypt on read and encrypt on write:
|
|
337
|
+
|
|
338
|
+
| store | where you get it |
|
|
339
|
+
| --- | --- |
|
|
340
|
+
| `ctx.store` | inside a handler |
|
|
341
|
+
| the boot store | an auth strategy, a plugin HTTP route, `bindDataStore` |
|
|
342
|
+
| a transaction view | inside `store.transactional(...)` |
|
|
343
|
+
|
|
344
|
+
The boot store is deliberately NOT the request-scoped one — it has no resolved
|
|
345
|
+
Subject, so it carries no tenant scope, no soft-delete filter, no audit stamping
|
|
346
|
+
and no row filter. It does carry the **storage codec**, because that needs no
|
|
347
|
+
Subject: it is how a declared column is spelled on disk versus in JS.
|
|
348
|
+
|
|
349
|
+
That split is worth knowing because getting it wrong is silent. Ciphertext is a
|
|
350
|
+
string. It compares, concatenates, renders and logs without complaint, so a
|
|
351
|
+
value read through a store that skipped the codec fails somewhere else entirely
|
|
352
|
+
— a team sent `enc:v1:…` upstream as a bearer token, got a 401, and spent a day
|
|
353
|
+
inside their auth code. If a value that should be plaintext arrives as
|
|
354
|
+
`enc:v1:…`, the question is which store produced it, not whether the column is
|
|
355
|
+
declared correctly.
|
|
356
|
+
|
|
357
|
+
A store you construct yourself from a driver has no codec. If you need one —
|
|
358
|
+
a migration script, a maintenance task — wrap it:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { wrapStoreWithBootCodec } from '@voltro/runtime'
|
|
362
|
+
|
|
363
|
+
const store = wrapStoreWithBootCodec(rawDriverStore, 'postgres')
|
|
364
|
+
```
|
|
365
|
+
|
|
333
366
|
## Encrypting a column that already has rows
|
|
334
367
|
|
|
335
368
|
`.encrypted()` encrypts on **write**. Adding it to a populated column converts
|
|
@@ -327,6 +327,52 @@ surfaces where the READ is the sensitive act — a GDPR export, a salary view
|
|
|
327
327
|
usually together with `include` so it stays targeted.
|
|
328
328
|
|
|
329
329
|
|
|
330
|
+
## Reading a redacted row — `'shape'`
|
|
331
|
+
|
|
332
|
+
`redactInput`, `redactOutcome` and `redactSubject` default to removing the
|
|
333
|
+
payload entirely (`{ "__redacted": "all" }`). That is the right default and it
|
|
334
|
+
is unreadable on purpose.
|
|
335
|
+
|
|
336
|
+
`'shape'` is the middle option: the payload's STRUCTURE, no value from it.
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
auditPlugin({ redactOutcome: 'shape', redactInput: 'shape' })
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{ "__redacted": { "jiraToken": "string(113)", "attempts": "number" } }
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
It exists because a team spent a day on a bug their own trail could have ended
|
|
347
|
+
in seconds: a value arrived as 113 characters where 44 were due, and the row
|
|
348
|
+
that would have said so read `{"__redacted":"all"}`. They had asked for that
|
|
349
|
+
redaction one round earlier, and both requests were right.
|
|
350
|
+
|
|
351
|
+
What it discloses, and what it never does:
|
|
352
|
+
|
|
353
|
+
| | |
|
|
354
|
+
| --- | --- |
|
|
355
|
+
| a string | its LENGTH — `string(113)`. Never a prefix, never a hash |
|
|
356
|
+
| a number, a boolean, a date | its TYPE only. A number can BE the secret |
|
|
357
|
+
| a declared field name | survives — `jiraToken` |
|
|
358
|
+
| a key that is not an identifier | described, not reproduced — `<key:string(36)>` |
|
|
359
|
+
| depth / breadth | capped, and the shape says where it stopped |
|
|
360
|
+
|
|
361
|
+
Two of those are worth a sentence each.
|
|
362
|
+
|
|
363
|
+
**A key can be the value.** An object keyed by user data (`{ "user@example.com":
|
|
364
|
+
… }`) puts a datum where a schema name belongs, so a key is reproduced only when
|
|
365
|
+
it looks like a declared field: a plain short identifier. A legitimate key that
|
|
366
|
+
is not one (`content-type`) loses its name and keeps its shape.
|
|
367
|
+
|
|
368
|
+
**A string's length is a real disclosure, and a small one.** For a fixed-format
|
|
369
|
+
credential it carries nothing — every token of a given kind is the same length.
|
|
370
|
+
For a human-chosen password it is a weak hint. If that matters in your threat
|
|
371
|
+
model, `'all'` is the default and stays available.
|
|
372
|
+
|
|
373
|
+
`redactSubject` spells its variant `'metadata-shape'`, because its default
|
|
374
|
+
(`'metadata'`) already names the field it acts on.
|
|
375
|
+
|
|
330
376
|
## Scoping the trail — `scope`
|
|
331
377
|
|
|
332
378
|
```ts
|