@voltro/cli 0.33.0 → 0.35.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 +1968 -0
- package/bin/nodeEnvironment.d.mts +30 -0
- package/bin/nodeEnvironment.mjs +158 -0
- package/bin/voltro.mjs +69 -5
- package/dist/addCommand-BNeoeSxe.js +124 -0
- package/dist/addCommand-aXSQveak.js +2 -0
- package/dist/agentsMd-BTchIZku.js +2 -0
- package/dist/agentsMd-mhQMF1bx.js +254 -0
- package/dist/apiBuild-B8aoJvuw.js +2 -0
- package/dist/{apiBuild-h9VHtnlw.js → apiBuild-DYD_ONLD.js} +46 -46
- package/dist/{appGraph-gQ_6GkQQ.js → appGraph-KGDPTuTy.js} +1 -1
- package/dist/appGraph-zuMGKVYX.js +2 -0
- package/dist/appPort-B_HpJ_ck.js +48 -0
- package/dist/baselineCommand-C2ClWZN3.js +2 -0
- package/dist/baselineCommand-DIttzO8A.js +227 -0
- package/dist/bin.js +71 -28
- package/dist/build-CD8K4XOr.js +711 -0
- package/dist/cacheCommand-DA4OH9xt.js +42 -0
- package/dist/capabilitiesCommand-nq_pz5xd.js +123 -0
- package/dist/checkCommand-Ct9xkTrS.js +232 -0
- package/dist/checkCommand-DKpDLlqu.js +2 -0
- package/dist/{cliArgs-qdZSElM3.js → cliArgs-D4p8n7EE.js} +12 -1
- package/dist/cliError-BmdYnghb.js +10 -0
- package/dist/cliOutput-D1tSBoRM.js +15 -0
- package/dist/{cliRuntime-Oh517vCV.js → cliRuntime-Dh7UDinH.js} +20 -20
- package/dist/cloudClient-DWL-Hw_T.js +67 -0
- package/dist/cloudCmd-Cvv5HGaZ.js +364 -0
- package/dist/clusterCmd-D5wsCmA_.js +54 -0
- package/dist/codegen-CYM3Zqrf.js +605 -0
- package/dist/codegen-ChBi_hVa.js +2 -0
- package/dist/codegenCommand-C4YoQIc2.js +30 -0
- package/dist/codemodRunner-BnFq3Fgu.js +5384 -0
- package/dist/commandRunner-BLAEFLjp.js +47 -0
- package/dist/commands-BE8E7zF3.js +816 -0
- package/dist/connectionConfig-UFlIEiys.js +66 -0
- package/dist/dashboardCommand-D7SgZGaN.js +25 -0
- package/dist/dataCommand-BhYwDgg-.js +537 -0
- package/dist/dataProfile-dW-PsfLB.js +15 -0
- package/dist/dbCommand-DS4b97Is.js +2 -0
- package/dist/{dbCommand-DTLKAfbA.js → dbCommand-O8HA63s2.js} +552 -402
- package/dist/{dev-C_P8FLSx.js → dev-C7sFZq3m.js} +3670 -3169
- package/dist/dev-D2BikO7a.js +3 -0
- package/dist/devActivity-Dx_3nnGv.js +100 -0
- package/dist/devActivity.js +1 -1
- package/dist/dialectDriver-CgXnDfec.js +39 -0
- package/dist/discover-C9XKJDco.js +25 -0
- package/dist/doctorCommand-CM4Ch9C7.js +2 -0
- package/dist/{checkCommand-xGhRFFg2.js → doctorCommand-DnimF5IM.js} +523 -1278
- package/dist/dormancyCommand-QewYug_s.js +69 -0
- package/dist/e2eCmd-BRabZww-.js +147 -0
- package/dist/embeddingsCommand-BfiLS_QI.js +73 -0
- package/dist/envCommand-CCGPRQY1.js +60 -0
- package/dist/evalCommand-6RUfPen4.js +118 -0
- package/dist/evolveCommand-CHsLCtDf.js +281 -0
- package/dist/fileTaxonomy-CJfgOllU.js +457 -0
- package/dist/frameworkTableAssembly-YyVe32Cb.js +2 -0
- package/dist/frameworkTableAssembly-oMPBKqlE.js +511 -0
- package/dist/generateCommand-DbgcUpGw.js +147 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +4 -3
- package/dist/infoCommand-DwOgK1t6.js +60 -0
- package/dist/{inspect-BUUjt773.js → inspect-CjTYzAs_.js} +113 -41
- package/dist/inspect-P4pxoMaV.js +2 -0
- package/dist/inspectCmd-EHFZ9yYu.js +224 -0
- package/dist/inspectFetch-EMuhTG_9.js +151 -0
- package/dist/inspectMetrics-CfdKLh6t.js +72 -0
- package/dist/loadEnv-D9nEOClM.js +44 -0
- package/dist/logFileSink-C_D2wRN1.js +105 -0
- package/dist/logsCmd-D36xK7Zu.js +260 -0
- package/dist/manifestBuild-COkJoyAr.js +2 -0
- package/dist/{manifestBuild-BLrVuSlM.js → manifestBuild-hpPLaGxV.js} +1 -1
- package/dist/metaCommands-7MJfZ5cf.js +196 -0
- package/dist/migrate-BV7I-ZHZ.js +83 -0
- package/dist/mssqlClusterPatch-_4cE_nun.js +44 -0
- package/dist/newCommand-COWOJ1_E.js +156 -0
- package/dist/nodeEnvironment-cGFAj1J8.js +28 -0
- package/dist/packageCommand-Cug_3Ogl.js +271 -0
- package/dist/pageConvention-cEiRxdab.js +5 -0
- package/dist/privacyCommand-C-Df56U_.js +146 -0
- package/dist/probeCommand-CZfaaUOZ.js +122 -0
- package/dist/projectScaffold-DmzEKHib.js +2 -0
- package/dist/projectScaffold-LMMtaavR.js +814 -0
- package/dist/renderModeScan-D7J1B7Kw.js +105 -0
- package/dist/renderProfile-1OWWAAtx.js +81 -0
- package/dist/runtimeRegistry-DMeKfTHP.js +81 -0
- package/dist/runtimeTrace-CH3eUiMw.js +91 -0
- package/dist/scheduleCmd-DQRu6BZC.js +149 -0
- package/dist/scheduleManifestCmd-D2x0CTTY.js +249 -0
- package/dist/schemaIr-UJybUUZW.js +103 -0
- package/dist/{sdkgen-C81QIkiL.js → sdkgen-BLkvGRfX.js} +111 -209
- package/dist/seedRunner-ZmLSqNe2.js +333 -0
- package/dist/serveCommand-CbDHU6l-.js +2 -0
- package/dist/serveCommand-iwlUBNS1.js +1766 -0
- package/dist/serveEntry.js +5 -5
- package/dist/serverlessCommand-CfJZy6dS.js +482 -0
- package/dist/start-BgN62boB.js +3 -0
- package/dist/start-T4VesWiM.js +1087 -0
- package/dist/startEntry.js +2 -2
- package/dist/staticCommand-Dr2M6tpU.js +304 -0
- package/dist/storageCommand-Co6NfLqN.js +42 -0
- package/dist/templates-De8IR5-c.js +102 -0
- package/dist/test-CI6iDsYc.js +115 -0
- package/dist/tracesCmd-DStmCJPi.js +232 -0
- package/dist/tsconfigPaths-BWXBWgcl.js +107 -0
- package/dist/tsxLoader-EuXmSJ1K.js +51 -0
- package/dist/typecheckCommand-BlsWiCNq.js +61 -0
- package/dist/updateCommand-BlMZhWgO.js +2 -0
- package/dist/updateCommand-x0pI_x-B.js +585 -0
- package/dist/webDev-BcykISYQ2.js +2 -0
- package/dist/{inspectMetrics-1xzTKAFx.js → webDev-Dybxew86.js} +988 -1560
- package/dist/webhookDiscovery-CrGAfhIG.js +2 -0
- package/dist/webhookDiscovery-D7VaeMlz.js +51 -0
- package/dist/webhooksCommand-DlAgS2Iw.js +267 -0
- package/dist/workflowsCmd-BGF-mRZ5.js +608 -0
- package/package.json +209 -17
- package/templates/AGENTS.core.md +58 -3
- package/templates/AGENTS.md +64 -7
- package/templates/agent-docs/_index.md +6 -4
- package/templates/agent-docs/_manifest.json +23 -6
- package/templates/agent-docs/ai.md +370 -0
- package/templates/agent-docs/authentication.md +313 -31
- package/templates/agent-docs/caching.md +6 -0
- package/templates/agent-docs/cli.md +853 -50
- package/templates/agent-docs/data.md +608 -12
- package/templates/agent-docs/database/migrations.md +223 -25
- package/templates/agent-docs/database/misc.md +156 -40
- package/templates/agent-docs/database/querying.md +19 -1
- package/templates/agent-docs/database/scaling.md +60 -0
- package/templates/agent-docs/database/schema.md +1 -0
- package/templates/agent-docs/database/seedsdialects.md +208 -19
- package/templates/agent-docs/database/transactions.md +68 -0
- package/templates/agent-docs/deployment.md +284 -25
- package/templates/agent-docs/introduction.md +88 -17
- package/templates/agent-docs/local-first-mobile.md +79 -4
- package/templates/agent-docs/multi-tenancy.md +188 -42
- package/templates/agent-docs/observability.md +58 -3
- package/templates/agent-docs/plugins/ai-flows.md +247 -2
- package/templates/agent-docs/plugins/analytics-postgres.md +1 -1
- package/templates/agent-docs/plugins/audit.md +37 -1
- package/templates/agent-docs/plugins/auth-social.md +143 -0
- package/templates/agent-docs/plugins/auth-workos.md +4 -2
- package/templates/agent-docs/plugins/auth.md +131 -6
- package/templates/agent-docs/plugins/billing.md +132 -15
- package/templates/agent-docs/plugins/cdc-out.md +46 -7
- package/templates/agent-docs/plugins/clickhouse.md +32 -1
- package/templates/agent-docs/plugins/duckdb.md +1 -1
- package/templates/agent-docs/plugins/flags.md +132 -0
- package/templates/agent-docs/plugins/governance.md +105 -7
- package/templates/agent-docs/plugins/multitenancy.md +9 -4
- package/templates/agent-docs/plugins/presence.md +13 -2
- package/templates/agent-docs/plugins/ratelimit.md +9 -0
- package/templates/agent-docs/plugins/search.md +162 -8
- package/templates/agent-docs/plugins/sso-saml.md +47 -8
- package/templates/agent-docs/plugins/storage.md +11 -0
- package/templates/agent-docs/plugins/webhooks.md +105 -0
- package/templates/agent-docs/plugins.md +152 -18
- package/templates/agent-docs/reference.md +60 -3
- package/templates/agent-docs/releases.md +1117 -0
- package/templates/agent-docs/routing.md +43 -25
- package/templates/agent-docs/scheduling.md +27 -0
- package/templates/agent-docs/schema-driven-ui.md +92 -12
- package/templates/agent-docs/security.md +426 -0
- package/templates/agent-docs/templates/apibackends.md +87 -18
- package/templates/agent-docs/templates/appshells.md +32 -14
- package/templates/agent-docs/templates/overview.md +13 -8
- package/templates/agent-docs/testing.md +211 -14
- package/templates/agent-docs/whats-new.md +98 -136
- package/templates/agent-docs/workflows.md +231 -14
- package/templates/apps/api-ai/actions/summarize.action.ts +11 -0
- package/templates/apps/api-ai/package.json +8 -7
- package/templates/apps/api-auth/actions/me.action.ts +13 -0
- package/templates/apps/api-auth/package.json +9 -8
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend/package.json +12 -8
- package/templates/apps/api-backend/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +14 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +8 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +10 -0
- package/templates/apps/api-backend-deactivation/package.json +8 -7
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +17 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-mail/package.json +9 -8
- package/templates/apps/api-backend-mail/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-mariadb/.env.example +14 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-mariadb/package.json +10 -9
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-sqlite/.env.example +14 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-sqlite/package.json +9 -8
- package/templates/apps/api-backend-sqlite/queries/notes.query.ts +30 -8
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +13 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +12 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +9 -0
- package/templates/apps/api-backend-storage/package.json +9 -8
- package/templates/apps/api-backend-storage/queries/notes.query.ts +30 -8
- package/templates/apps/api-cms/actions/content.get.action.ts +7 -0
- package/templates/apps/api-cms/actions/content.types.action.ts +6 -0
- package/templates/apps/api-cms/actions/me.action.ts +13 -0
- package/templates/apps/api-cms/app.config.ts +19 -0
- package/templates/apps/api-cms/authz.ts +63 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.ts +10 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +10 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +5 -0
- package/templates/apps/api-cms/package.json +11 -10
- package/templates/apps/api-cms/queries/content.list.query.ts +23 -7
- package/templates/apps/api-cms/tests/accessDecisions.test.ts +121 -0
- package/templates/apps/api-cms/tests/content.descriptors.test.ts +8 -4
- package/templates/apps/api-collab/mutations/documents.create.mutation.ts +8 -0
- package/templates/apps/api-collab/mutations/documents.setBody.mutation.ts +13 -0
- package/templates/apps/api-collab/package.json +9 -8
- package/templates/apps/api-collab/queries/documents.query.ts +19 -7
- package/templates/apps/api-data-advanced/package.json +9 -8
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +12 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +9 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +17 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +9 -0
- package/templates/apps/api-durable/package.json +9 -8
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +16 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +12 -0
- package/templates/apps/api-feature-flags/package.json +10 -9
- package/templates/apps/api-governance/README.md +30 -8
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -1
- package/templates/apps/api-governance/actions/profiles.get.action.ts +29 -4
- package/templates/apps/api-governance/database/schema.ts +16 -4
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +12 -0
- package/templates/apps/api-governance/package.json +9 -8
- package/templates/apps/api-kv/actions/sync.pull.action.ts +15 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +13 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +7 -0
- package/templates/apps/api-kv/package.json +9 -8
- package/templates/apps/api-kv/queries/events.list.query.ts +19 -8
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +11 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +13 -0
- package/templates/apps/api-moderation/package.json +9 -8
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +8 -0
- package/templates/apps/api-observability/package.json +9 -8
- package/templates/apps/api-observability/queries/notes.list.query.ts +13 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +14 -0
- package/templates/apps/api-ratelimit/package.json +9 -8
- package/templates/apps/api-rbac/package.json +9 -8
- package/templates/apps/api-rest/package.json +8 -7
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +13 -0
- package/templates/apps/api-saas/package.json +12 -11
- package/templates/apps/api-saas/queries/projects.list.query.ts +11 -0
- package/templates/apps/api-saas-starter/actions/me.action.ts +13 -0
- package/templates/apps/api-saas-starter/app.config.ts +19 -0
- package/templates/apps/api-saas-starter/authz.ts +75 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +10 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +9 -0
- package/templates/apps/api-saas-starter/package.json +15 -11
- package/templates/apps/api-saas-starter/queries/invites.list.query.ts +19 -8
- package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -7
- package/templates/apps/api-saas-starter/tests/accessDecisions.test.ts +135 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +14 -0
- package/templates/apps/api-search/package.json +9 -8
- package/templates/apps/api-search/queries/articles.list.query.ts +21 -8
- package/templates/apps/api-status/README.md +10 -3
- package/templates/apps/api-status/app.config.ts +8 -3
- package/templates/apps/api-status/authz.ts +5 -3
- package/templates/apps/api-status/package.json +9 -8
- package/templates/apps/api-status/queries/components.list.query.ts +14 -6
- package/templates/apps/api-status/queries/incidents.live.query.ts +23 -10
- package/templates/apps/api-status/queries/updates.list.query.ts +16 -9
- package/templates/apps/api-status/tests/status.test.ts +9 -1
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +12 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +13 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +12 -0
- package/templates/apps/api-versioning/package.json +9 -8
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +16 -0
- package/templates/apps/api-webhooks/package.json +10 -9
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +10 -0
- package/templates/apps/changelog/package.json +8 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +10 -8
- package/templates/apps/frontend-admin/src/lib/admin.ts +20 -9
- package/templates/apps/frontend-admin/src/locales/de.ts +11 -1
- package/templates/apps/frontend-admin/src/locales/en.ts +13 -1
- package/templates/apps/frontend-admin/src/pages/admin/[entity]/page.tsx +65 -22
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +130 -23
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +3 -2
- package/templates/apps/frontend-admin/src/pages/admin/page.test.tsx +19 -2
- package/templates/apps/frontend-admin/src/pages/admin/page.tsx +9 -4
- package/templates/apps/frontend-app/app.config.ts +4 -3
- package/templates/apps/frontend-app/package.json +11 -8
- package/templates/apps/frontend-app/src/lib/api.ts +25 -0
- package/templates/apps/frontend-app/src/pages/page.test.tsx +130 -82
- package/templates/apps/frontend-app/src/pages/page.tsx +14 -18
- package/templates/apps/frontend-auth/package.json +10 -8
- package/templates/apps/frontend-blank/package.json +9 -7
- package/templates/apps/frontend-cms/package.json +11 -9
- package/templates/apps/frontend-collab/package.json +12 -9
- package/templates/apps/frontend-collab/src/pages/page.test.tsx +122 -78
- package/templates/apps/frontend-contact/package.json +9 -7
- package/templates/apps/frontend-dashboard/package.json +9 -7
- package/templates/apps/frontend-docs/package.json +9 -7
- package/templates/apps/frontend-i18n/package.json +8 -6
- package/templates/apps/frontend-landing/package.json +9 -7
- package/templates/apps/frontend-portal/package.json +10 -8
- package/templates/apps/frontend-saas/app.config.ts +10 -6
- package/templates/apps/frontend-saas/package.json +10 -8
- package/templates/apps/frontend-saas/src/lib/api.ts +27 -32
- package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +7 -8
- package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +27 -3
- package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +4 -4
- package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +3 -4
- package/templates/apps/frontend-spa/package.json +9 -7
- package/templates/apps/frontend-ssr/package.json +9 -7
- package/templates/apps/frontend-ssr-api/package.json +10 -8
- package/templates/apps/frontend-static-blog/package.json +8 -6
- package/templates/apps/frontend-status/package.json +10 -8
- package/templates/apps/mobile-app/README.md +1 -0
- package/templates/apps/mobile-app/package.json +4 -2
- package/templates/apps/mobile-app/src/app/index.tsx +22 -12
- package/templates/apps/mobile-app/src/app/orders/[id].tsx +1 -1
- package/templates/apps/mobile-app/src/lib/api.ts +34 -0
- package/templates/apps/mobile-app/voltro.mobile.ts +4 -2
- package/templates/baselines/bare/.env.example +14 -0
- package/templates/baselines/bare/baseline.json +4 -4
- package/templates/baselines/compose/.env.example +14 -0
- package/templates/baselines/compose/README.md +1 -1
- package/templates/baselines/compose/baseline.json +5 -5
- package/templates/baselines/compose-mariadb/.env.example +14 -0
- package/templates/baselines/compose-mariadb/README.md +1 -1
- package/templates/baselines/compose-mariadb/baseline.json +5 -5
- package/templates/baselines/helm/.env.example +14 -0
- package/templates/baselines/helm/baseline.json +4 -4
- package/dist/apiBuild-C-x9YacA.js +0 -2
- package/dist/appGraph-CvQCte0z.js +0 -2
- package/dist/checkCommand-DRovTKza.js +0 -2
- package/dist/commands-CJfepbm4.js +0 -11541
- package/dist/dbCommand-b1gum4td.js +0 -2
- package/dist/dev-iiMtlkfs.js +0 -3
- package/dist/devActivity-BhIu6ncs.js +0 -159
- package/dist/frameworkTableAssembly-BwIrO5nv.js +0 -638
- package/dist/frameworkTableAssembly-D-EebUQX.js +0 -2
- package/dist/inspect-mmBuRXmy.js +0 -2
- package/dist/manifestBuild-Dj8Jjoto.js +0 -2
- package/dist/seedRunner-Bqxgp7HZ.js +0 -230
- package/dist/serveCommand-DdaM4Hup.js +0 -1608
- package/dist/start-C0koT0UO.js +0 -1084
- /package/templates/apps/api-ai/actions/{summarize.action.server.tsx → summarize.action.server.ts} +0 -0
|
@@ -15,11 +15,11 @@ The Voltro CLI is the single entry point for scaffolding, dev, build, and run. I
|
|
|
15
15
|
|
|
16
16
|
## Command quick-reference
|
|
17
17
|
|
|
18
|
-
The dispatcher routes `voltro <command> [args]` to the matching subcommand and passes the rest through. The
|
|
18
|
+
The dispatcher routes `voltro <command> [args]` to the matching subcommand and passes the rest through. **`voltro help` is the authority** — it prints the registry itself, so it cannot drift from what your installed CLI dispatches. The table below is every command that registry exposes, grouped by purpose:
|
|
19
19
|
|
|
20
20
|
| Group | Commands |
|
|
21
21
|
|---|---|
|
|
22
|
-
| [Scaffolding](/docs/cli/scaffolding) | `create-project`, `add-app`, `list-templates`, `new` (scaffold one primitive — `query` / `mutation` / `action` / `workflow` / `page`) |
|
|
22
|
+
| [Scaffolding](/docs/cli/scaffolding) | [`init`](/docs/cli/scaffolding#voltro-init) (initialise the current directory as a workspace root — no apps), `create-project`, `add-app`, `list-templates`, `new` (scaffold one primitive — `query` / `mutation` / `action` / `workflow` / `page`) |
|
|
23
23
|
| Packages | `package` (`create` / `publishable` / `private` / `status`), `create-package` |
|
|
24
24
|
| [Dev](/docs/cli/dev) | `dev`, `codegen`, `typecheck` (`tsc --noEmit` with the app's own TypeScript), `agents-md`, [`env`](/docs/cli/env) (`check` / `sync` / `types` / `turbo`), [`generate`](/docs/cli/scaffolding) (AI app-builder), `dashboard` (serve the DevTools dashboard standalone; `--port`, `VOLTRO_DASHBOARD_APPS`) |
|
|
25
25
|
| [Build & run](/docs/cli/build-and-start) | `build`, `start`, `serve` |
|
|
@@ -27,20 +27,61 @@ The dispatcher routes `voltro <command> [args]` to the matching subcommand and p
|
|
|
27
27
|
| [Database](/docs/cli/migrate) | `migrate`, `db` (`plan` / `apply` / `plans` / `drift` / `squash` / `restore-snapshot` / `migrate` / `rollback` / `status` / `seed`), [`evolve`](/docs/database/migrations/rename-and-drop) (schema-evolution copilot — propose a codemod + branch-verified backfill for a rename / retype / split / drop of an existing column or table) |
|
|
28
28
|
| [Update](/docs/cli/update) | `update` (`--to` / `--dry-run` / `--force` / `--exact`) — bump every `@voltro/*`, install, run the codemods that adapt your source to the new version |
|
|
29
29
|
| [Data transfer](/docs/cli/data) | `data` (`export` / `import` / `unpack` / `inspect` / `backup` / `restore`) — directory + single-file `.vbundle` bundles, streaming assets, masking, at-rest encryption |
|
|
30
|
-
| Ops / infra | `cache` (`status` / `flush` / `invalidate`), `add` (`redis`), `baseline` (`list` / `status` / `set`), `schedule-manifest`, [`storage`](/docs/plugins/storage) (`doctor` / `cors`) |
|
|
30
|
+
| Ops / infra | `cache` (`status` / `flush` / `invalidate`), `add` (`redis`), `baseline` (`list` / `status` / `set`), `schedule` (`run <name>` — fire a registered schedule on demand; `--process` / `--trigger manual\|external` / `--url`), `schedule-manifest`, [`storage`](/docs/plugins/storage) (`doctor` / `cors`) |
|
|
31
31
|
| AI / data | `embeddings backfill <table> --text <field> --vector <field>` — (re)embed rows the `vectorEmbedding()` mixin missed (pre-existing rows / a model change); `--dry-run` to preview; [`eval`](/docs/ai/agents#evaluating-recorded-runs-voltro-eval) — replay recorded agent runs against golden cases + judge, exit 1 on regression (a deploy gate; `--json` / `--branch` / `--threshold`) |
|
|
32
|
+
| Integrate | [`webhooks`](/docs/plugins/webhooks#voltro-webhooks-consumer--the-package-your-subscribers-install) (`consumer` / `events`) — generate the ZERO-dependency Standard-Webhooks verification package your subscribers install, from your own declared events (`--out` / `--name`); list the events a subscriber can register for (`--json`) |
|
|
32
33
|
| [Inspect & debug](/docs/cli/inspect) | `inspect`, `logs`, `traces`, `workflows`, `cluster`, `check` |
|
|
33
34
|
| [Health & surface](/docs/cli/build-and-start) | [`doctor`](/docs/cli/build-and-start) — serve preflight + the hand-roll detector (names the shipped primitive at the spot you're rebuilding it); [`capabilities`](/docs/cli/build-and-start) (`--json`) — the export surface read from your installed `@voltro/*`, so it can be verified instead of recalled; `info` (`--json`) — CLI / node / package-manager / dialect + every installed `@voltro/*` version, flagging lockstep skew (exits 1 on skew) |
|
|
34
35
|
| Harness | `test`, `e2e` |
|
|
35
36
|
| Cloud | `cloud` (`login` / `whoami` / `projects` / `env` / `import`) |
|
|
36
37
|
| Secrets | `secret` (`generate [purpose]` — the right var+format per secret; `generate` alone → a generic secret; `list`) |
|
|
37
|
-
| Meta | `agents-md`, `version`, `help` |
|
|
38
|
+
| Meta | `agents-md`, `telemetry` (reports that Voltro collects none — no phone-home, nothing to opt out of), `version`, `help` |
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
**Two commands are hidden from `voltro help` on purpose** and are not in the
|
|
41
|
+
table: `prune-runtime <deploy-dir>` (trims a deployed web tree's `node_modules`
|
|
42
|
+
to the reachable runtime set — the standalone Dockerfiles run it for you) and
|
|
43
|
+
`_apply-codemods` (the re-exec target `voltro update` uses to run the target
|
|
44
|
+
version's codemods). Both are dispatchable; neither is something you invoke
|
|
45
|
+
directly.
|
|
46
|
+
|
|
47
|
+
Each command takes an optional path argument (the app directory) — defaults to `.` when run from inside an app. `voltro init` initialises the current directory as a workspace root — `pnpm-workspace.yaml`, a root `package.json` with `dev`/`build`/`test`/`typecheck`, a `.gitignore` and `git init`, idempotently and without scaffolding any apps (`voltro create-project <name>` does that, and bootstraps the same root when there isn't one). `voltro secret generate [purpose]` prints a cryptographically strong secret — with a purpose (`data-transfer`, `session`, `bundle-key`, `field-encryption`, `storage`, `inspect`) it emits the correct env var + length/format as a paste-ready `NAME=value` (`voltro secret list` shows them all); with no purpose, a generic base64url secret. `voltro telemetry` reports that Voltro collects none. `voltro deploy` shows the deploy paths — self-host via a [baseline](/docs/deployment/baselines) + CI, managed via the control-plane client `voltro cloud` (managed cloud deploy is coming soon — see [Voltro Cloud](/docs/deployment/voltro-cloud)), individual [serverless functions](/docs/deployment/serverless-functions) (`voltro serverless` → self-hosted Node, or Cloudflare / Scaleway), or a [static site](/docs/deployment/static-sites) (`voltro static` → Cloudflare Pages / S3 / Netlify).
|
|
40
48
|
|
|
41
49
|
## Help + version
|
|
42
50
|
|
|
43
|
-
`voltro help` prints the command list; `voltro version` prints the CLI version.
|
|
51
|
+
`voltro help` prints the command list; `voltro version` prints the CLI version. Both are also reachable as bare flags — `voltro --help` / `-h` and `voltro --version` / `-v` — handled by the dispatcher before it looks for a subcommand.
|
|
52
|
+
|
|
53
|
+
**Per-command help** is the useful one. `voltro <command> --help` (equivalently `voltro help <command>`) prints that command's usage line, its flags, the environment variables that change what it does, and worked examples:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
voltro dev --help
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
voltro dev — Start the local development server (auto-discovers routes / mutations).
|
|
61
|
+
|
|
62
|
+
Usage
|
|
63
|
+
voltro dev [app]
|
|
64
|
+
|
|
65
|
+
Examples
|
|
66
|
+
voltro dev
|
|
67
|
+
boot the app in the current directory
|
|
68
|
+
voltro dev apps/acme/api
|
|
69
|
+
boot a specific app
|
|
70
|
+
|
|
71
|
+
Environment
|
|
72
|
+
PORT override the port from app.config.ts
|
|
73
|
+
WATCH=0 run the server directly, with no file-watching supervisor
|
|
74
|
+
WATCH_POLL=1 poll instead of using native FS events (bind-mounts: k8s hostPath / Docker)
|
|
75
|
+
…
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`--help` never runs the command. A command with subcommands of its own (`db`, `cloud`, `workflows`, `inspect`, `update`, …) prints its own richer page instead.
|
|
79
|
+
|
|
80
|
+
## Startup
|
|
81
|
+
|
|
82
|
+
The CLI loads a command's implementation only when you dispatch it, so `voltro version`, `voltro info` and `voltro new` do not pay for the dev server, the build toolchain or the runtime. The same applies to the `voltro dev` supervisor's respawned child on every save.
|
|
83
|
+
|
|
84
|
+
The CLI's first line is its own — no Node warnings ahead of it.
|
|
44
85
|
|
|
45
86
|
## Common per-command flags
|
|
46
87
|
|
|
@@ -57,7 +98,11 @@ Commands like `build` / `start` / `migrate` / `codegen` parse no flags at all
|
|
|
57
98
|
|
|
58
99
|
## Common env vars
|
|
59
100
|
|
|
60
|
-
The
|
|
101
|
+
The framework reads more than a hundred distinct `VOLTRO_*` / `DB_*` / `PG_*`
|
|
102
|
+
variables across its packages, so this is **not** the full set and could not
|
|
103
|
+
usefully be — it is the ones you reach for. Each subsystem's page carries its own; the complete transport-security
|
|
104
|
+
and connection lists live in [Production hardening](/docs/deployment/production-hardening)
|
|
105
|
+
and [Security](/docs/security/overview).
|
|
61
106
|
|
|
62
107
|
| Var | Effect |
|
|
63
108
|
|---|---|
|
|
@@ -72,6 +117,7 @@ The CLI reads:
|
|
|
72
117
|
| `VOLTRO_INSPECT_TOKEN` | Bearer for the inspect surface. **Fail-closed:** unset → every request is `401`. `voltro dev` mints one per project; `voltro serve` / `voltro start` mint nothing, so a public deploy is closed by default (set it explicitly to open the surface). |
|
|
73
118
|
| `VOLTRO_INSPECT_ALLOWED_HOSTS` | Extra `Host` names allowed to reach the **dev** inspect surface, past its DNS-rebinding guard (comma/space-separated). Loopback names + IP literals are always allowed; any other domain name is refused unless listed here — the api counterpart of vite's `allowedHosts`. |
|
|
74
119
|
| `DB_URL` | Database connection string (falls back to `DB_PRIMARY_URL`; or the discrete `DB_*` / `PG_*` fields). |
|
|
120
|
+
| `DB_ACQUIRE_TIMEOUT_MS` | How long a request may wait for a free pooled connection before failing (default `10000`; `0` restores the driver's own unbounded wait). Read on every command and every dialect that can bound it. `DB_ACQUIRE_QUEUE_LIMIT` is its mysql/mariadb counterpart and is deliberately **unset** by default. |
|
|
75
121
|
| `VOLTRO_SESSION_SECRET` | Session-cookie signing secret (`@voltro/plugin-auth`). Rotate with zero downtime: move the old value to `VOLTRO_SESSION_SECRET_PREVIOUS` for one session lifetime — cookies signed with either secret keep verifying, and previous-key cookies are re-issued under the new one. |
|
|
76
122
|
| `VOLTRO_DATA_TRANSFER_SECRET` | Gates the prod data-transfer endpoints (`POST /_voltro/admin/{export,import}`); ≥16 chars or the routes don't mount. |
|
|
77
123
|
| `VOLTRO_BUNDLE_KEY` | Passphrase for `.vbundle` export encryption (`voltro data export --encrypt`). A DEDICATED key, not the transfer secret. |
|
|
@@ -79,6 +125,24 @@ The CLI reads:
|
|
|
79
125
|
| `VOLTRO_STORAGE_SECRET` | Signs storage grant tokens (private files); falls back to the session secret if unset. |
|
|
80
126
|
| `AI_PROVIDER` / `AI_MODEL` / `AI_API_KEY` | Per-provider AI config. |
|
|
81
127
|
|
|
128
|
+
### Transport security — the overrides on by default since 0.34.0
|
|
129
|
+
|
|
130
|
+
The api listener's cross-site check, proxy policy and security headers are **on
|
|
131
|
+
by default** and configured in `app.config.ts`'s `security` block. Each has an
|
|
132
|
+
env override, resolved identically under `dev` and `serve`, with the explicit
|
|
133
|
+
config value always winning:
|
|
134
|
+
|
|
135
|
+
| Var | Effect |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `VOLTRO_ORIGIN_GUARD` | `off` disables the cross-site origin check. Anything else leaves the default `same-origin`. |
|
|
138
|
+
| `VOLTRO_ALLOWED_ORIGINS` | Comma-separated origins allowed past that check — what a web app on a different origin than the api needs. |
|
|
139
|
+
| `VOLTRO_TRUSTED_PROXIES` | Which hops may set `x-forwarded-for` / `x-forwarded-proto`: a comma-separated CIDR/IP list, `private`, `*` (trust the leftmost token), or a hop **count** (`2`). **Unset means the header is ignored entirely** and the socket address wins — set it if you run behind an ingress AND rate-limit or audit per IP. |
|
|
140
|
+
| `VOLTRO_SECURITY_HEADERS` | `off` / `default` / `strict` — the whole header bundle's mode. |
|
|
141
|
+
| `VOLTRO_CSP` | Content-Security-Policy for non-HTML api responses. `off` drops just this one. |
|
|
142
|
+
| `VOLTRO_CSP_HTML` | CSP for HTML responses (under `strict` it defaults to the same policy as `VOLTRO_CSP`). `off` drops just this one. |
|
|
143
|
+
| `VOLTRO_HSTS` | `Strict-Transport-Security` value. `off` drops just this one. |
|
|
144
|
+
| `VOLTRO_MAX_RPC_BODY_BYTES` | Cap on the buffered `POST /rpc` JSON body (default 8 MiB) — an oversized body is refused `413` and never buffered past the cap. File uploads ride plugin routes with their own limits. |
|
|
145
|
+
|
|
82
146
|
Generate any of the secret vars above with `voltro secret generate <purpose>` (see [`secret`](#command-quick-reference)) — it picks the right length and format. A lower environment's secrets must always differ from production's.
|
|
83
147
|
|
|
84
148
|
## Workflow patterns
|
|
@@ -86,10 +150,10 @@ Generate any of the secret vars above with `voltro secret generate <purpose>` (s
|
|
|
86
150
|
### "I'm starting a new project"
|
|
87
151
|
|
|
88
152
|
```bash
|
|
153
|
+
mkdir acme && cd acme
|
|
89
154
|
pnpx voltro create-project acme --api api-backend --web frontend-landing
|
|
90
|
-
cd acme
|
|
91
155
|
pnpm install
|
|
92
|
-
pnpm dev #
|
|
156
|
+
pnpm dev # pnpm -r --parallel dev — voltro dev in every app at once
|
|
93
157
|
```
|
|
94
158
|
|
|
95
159
|
### "I want to add a docs site to my existing project"
|
|
@@ -97,7 +161,7 @@ pnpm dev # turbo runs voltro dev across every app in the project
|
|
|
97
161
|
```bash
|
|
98
162
|
voltro add-app docs --template frontend-docs --to acme
|
|
99
163
|
pnpm install # picks up the new app's deps
|
|
100
|
-
pnpm dev #
|
|
164
|
+
pnpm dev # the new app joins the parallel boot automatically
|
|
101
165
|
```
|
|
102
166
|
|
|
103
167
|
### "I changed my schema and want to apply it"
|
|
@@ -266,10 +330,52 @@ The HTTP surface is reachable directly too — e.g. `curl -s localhost:4000/_vol
|
|
|
266
330
|
<!-- source: en/cli/scaffolding.md -->
|
|
267
331
|
## Scaffolding
|
|
268
332
|
|
|
269
|
-
|
|
333
|
+
_init, create-project, add-app, list-templates — boot new code with the framework's conventions baked in._
|
|
270
334
|
|
|
271
335
|
The scaffolder generates new projects + new apps from templates. Each template is a dogfooded reference; what you scaffold is the same shape the Voltro Cloud team uses.
|
|
272
336
|
|
|
337
|
+
## `voltro init`
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
voltro init # takes no arguments — it initialises the CURRENT directory
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Turns the directory you are standing in into a **Voltro workspace root**, and
|
|
344
|
+
scaffolds no apps at all. That is the whole distinction from `create-project`:
|
|
345
|
+
`init` prepares the root, `create-project` fills it. They share one
|
|
346
|
+
implementation (`ensureWorkspaceRoot`), so a greenfield `create-project` needs
|
|
347
|
+
no separate `init` — it bootstraps the same root when there isn't one.
|
|
348
|
+
|
|
349
|
+
What it writes, all of it **idempotent and additive**:
|
|
350
|
+
|
|
351
|
+
| File | Behaviour |
|
|
352
|
+
|---|---|
|
|
353
|
+
| `pnpm-workspace.yaml` | Written only when the walk up finds no workspace at all. |
|
|
354
|
+
| `package.json` (root) | Created when missing — `private`, `type: 'module'`, node/pnpm engines, the detected `packageManager`, the four scripts, `typescript` + `@types/node`. When it exists, only the **missing** keys are filled in; a script or a version range you already declared is never rewritten. |
|
|
355
|
+
| `tsconfig.base.json` | Written when missing. Every app + package tsconfig the framework generates extends this exact path, so `tsc` fails before it reads your code without it. |
|
|
356
|
+
| `.gitignore` | Created when missing; otherwise only the entries it does not already cover are appended. Includes `.env.local` — where `voltro dev` mints per-project secrets, and which must never be committed. |
|
|
357
|
+
| `git init` | Only when nothing at or above the directory is already a git working tree. |
|
|
358
|
+
|
|
359
|
+
The four root scripts are the workspace fan-outs:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{ "dev": "pnpm -r --parallel dev", "build": "pnpm -r build",
|
|
363
|
+
"test": "pnpm -r test", "typecheck": "pnpm -r typecheck" }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Two refusals, both deliberate:
|
|
367
|
+
|
|
368
|
+
- **A positional argument is an error** (`init takes no arguments — it
|
|
369
|
+
initialises the current directory`), with a hint pointing at
|
|
370
|
+
`voltro create-project <name>`. `voltro init acme` reads like "make me a
|
|
371
|
+
project called acme", and it is not that command.
|
|
372
|
+
- **It will not nest a second root inside an existing pnpm workspace.** If an
|
|
373
|
+
ancestor already has a `pnpm-workspace.yaml`, it names that root and tells you
|
|
374
|
+
to run `create-project` from there instead.
|
|
375
|
+
|
|
376
|
+
A second run on an already-initialised root prints `is already a Voltro
|
|
377
|
+
workspace root — nothing to do.`
|
|
378
|
+
|
|
273
379
|
## `create-project`
|
|
274
380
|
|
|
275
381
|
```bash
|
|
@@ -286,6 +392,7 @@ Bootstraps a new project under `apps/<name>/` with selected templates.
|
|
|
286
392
|
| `--baseline=<bare\|compose\|helm>` | prompts (or skip) | Deploy baseline to scaffold (`bare` / `compose` / `helm`). Without it, the interactive prompt lists the available ids. |
|
|
287
393
|
| `--port-range <start>-<end>` | `5190-5199` | Port range for web apps in this project. Persisted in `project.json`. Also accepts `:` / `..` separators. |
|
|
288
394
|
| `--no-input` | false | Skip prompts; suitable for CI / scripted scaffolding. |
|
|
395
|
+
| `--no-register` | false | Do not contact the Voltro Cloud control plane. See [registration](#project-registration) below. |
|
|
289
396
|
|
|
290
397
|
What it does:
|
|
291
398
|
|
|
@@ -304,6 +411,34 @@ pnpm install
|
|
|
304
411
|
pnpm dev
|
|
305
412
|
```
|
|
306
413
|
|
|
414
|
+
### Project registration
|
|
415
|
+
|
|
416
|
+
`create-project` and `add-app` finish by registering the project with the Voltro Cloud control plane. This is how **self-hosted** use is counted, and the Terms of Service ask for projects and apps to be registered. It is worth knowing exactly when it happens and what it involves, because it is part of your first command.
|
|
417
|
+
|
|
418
|
+
**If you are not logged in, nothing is sent.** No network call is attempted at all, and the scaffolder says so:
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
→ cloud registration: skipped — not logged in, so nothing was sent from this machine.
|
|
422
|
+
Registration is how SELF-HOSTED use is counted; the Voltro Cloud Terms of Service ask
|
|
423
|
+
for projects and apps to be registered once you have an account.
|
|
424
|
+
Register later: voltro cloud login then voltro cloud scan
|
|
425
|
+
Never ask again: scaffold with --no-register
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
**If you are logged in**, it prints the destination and the contents before the call:
|
|
429
|
+
|
|
430
|
+
```text
|
|
431
|
+
→ registering project 'acme' with https://cloud.voltro.dev (self-hosted usage tracking, ToS-governed)
|
|
432
|
+
Sends: the project slug, and per app its name, kind, framework version and the NAMES of
|
|
433
|
+
declared primitives (queries, mutations, tables, plugins, …) plus a page count.
|
|
434
|
+
Never sends: source code, row data, environment values or secrets.
|
|
435
|
+
Skip with --no-register.
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Pass `--no-register` to skip it entirely — appropriate for offline work, CI, or while evaluating. You can register later with `voltro cloud login` followed by `voltro cloud scan`.
|
|
439
|
+
|
|
440
|
+
This is the only network call the CLI makes on its own behalf; `voltro telemetry` reports the rest of the picture (the framework collects nothing).
|
|
441
|
+
|
|
307
442
|
### The seeded agent guide
|
|
308
443
|
|
|
309
444
|
`create-project` / `add-app` (and `voltro dev` on first boot) seed a guide that
|
|
@@ -543,6 +678,27 @@ lists the ones that reference none. It learns your own `require*` / `assert*`
|
|
|
543
678
|
guard names, so it does not report the call sites of guards you already wrote.
|
|
544
679
|
See [the authz scan](./build-and-start.md).
|
|
545
680
|
|
|
681
|
+
### When the database is not reachable
|
|
682
|
+
|
|
683
|
+
A refused, unresolvable or rejected database connection is reported as a condition with a fix, not as a framework crash:
|
|
684
|
+
|
|
685
|
+
```text
|
|
686
|
+
voltro: the database is not reachable at 127.0.0.1:5432 (ECONNREFUSED).
|
|
687
|
+
|
|
688
|
+
No database is configured — none of DB_URL / DB_HOST / PG_HOST is set in the
|
|
689
|
+
environment or in a loaded `.env`, so the framework used its local dev default.
|
|
690
|
+
|
|
691
|
+
Either start one: pnpm db:up (if your project ships a compose file)
|
|
692
|
+
or point at your own: DB_URL=postgres://user:pass@host:5432/dbname
|
|
693
|
+
— put it in `.env` next to app.config.ts, not just in one shell.
|
|
694
|
+
|
|
695
|
+
Re-run with --debug for the full stack.
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
When a variable **is** set, the message names it and says the address resolved and nothing answered there — which is a different problem from "is postgres running", and points you at the container, VPN or firewall instead. A server that answers and rejects you (wrong password, missing database) is reported as its own case, because the fix is different again.
|
|
699
|
+
|
|
700
|
+
Pass `--debug` (or set `VOLTRO_DEBUG=1`) to get the full Effect stack instead.
|
|
701
|
+
|
|
546
702
|
## `voltro dev <appDir>`
|
|
547
703
|
|
|
548
704
|
```bash
|
|
@@ -741,20 +897,22 @@ A `<DevtoolsStringsProvider strings={…}>` mounted above the overlay works too;
|
|
|
741
897
|
| `WATCH=0` | Disable filesystem watch. Useful under a parent watcher (Docker volume, devcontainer). |
|
|
742
898
|
| `VOLTRO_DASHBOARD=off` | Don't auto-launch the dashboard. |
|
|
743
899
|
| `VOLTRO_INSPECT=off` | Don't expose `/_voltro/inspect/*` endpoints. |
|
|
744
|
-
| `PORT=4001` | Override the listen port (api or web).
|
|
900
|
+
| `PORT=4001` | Override the listen port (api or web). See [Which port an app binds](#which-port-an-app-binds) for the full order. |
|
|
745
901
|
| `VOLTRO_DASHBOARD_PORT=5180` | Override the auto-launched dashboard port (default `5179`). |
|
|
746
902
|
| `VOLTRO_LOG_LEVEL=debug` | Verbose framework logs. |
|
|
747
903
|
| `VOLTRO_DEV_SSR_COMPILE_CONCURRENCY=4` | Max `ssr` pages compiled on demand at once (default `4`). Lower it (`1`/`2`) on a low-memory box if a burst of first-time `ssr` page loads spikes memory; raise it on a big machine. Warm (already-compiled) pages are never throttled. |
|
|
748
904
|
|
|
749
|
-
## Multi-app dev
|
|
905
|
+
## Multi-app dev
|
|
750
906
|
|
|
751
|
-
The `
|
|
907
|
+
The `dev` script the scaffolder writes at the workspace root is plain pnpm — no task runner to install:
|
|
752
908
|
|
|
753
909
|
```bash
|
|
754
|
-
pnpm dev # ↳
|
|
910
|
+
pnpm dev # ↳ pnpm -r --parallel dev — `voltro dev` in every app at once
|
|
755
911
|
```
|
|
756
912
|
|
|
757
|
-
|
|
913
|
+
`pnpm -r` selects by "has a `dev` script", so an app that owns its own dev loop (an Expo `mobile-app`, an `edge-functions` bundle) opts out simply by not defining one. To run a single app: `pnpm --filter @acme/api dev`.
|
|
914
|
+
|
|
915
|
+
If you prefer a task runner for its caching and per-app output panes, adding one is a normal workspace change — nothing in the framework depends on it.
|
|
758
916
|
|
|
759
917
|
## `voltro codegen <appDir>`
|
|
760
918
|
|
|
@@ -768,6 +926,24 @@ Regenerates `rpcGroup.generated.ts` (+ the web `.framework/*` entry) from the di
|
|
|
768
926
|
- **CI environments** where you want the typed client baked into a tarball before tests run.
|
|
769
927
|
- **Editor LSP confused** after a discovery pattern changed and the generated file went out of sync.
|
|
770
928
|
|
|
929
|
+
### Staleness is detected, not assumed
|
|
930
|
+
|
|
931
|
+
The generated file carries a `source-fingerprint` of the descriptor tree, so the other commands can tell whether it still matches your code:
|
|
932
|
+
|
|
933
|
+
- **`voltro build`** regenerates it when it is stale. A CI build from a clean checkout never ran `voltro dev`, and it is a file the build can produce itself.
|
|
934
|
+
- **`voltro test`** REFUSES and tells you to run `voltro codegen`:
|
|
935
|
+
|
|
936
|
+
```text
|
|
937
|
+
voltro test: …/rpcGroup.generated.ts is stale — a descriptor changed since the group was generated.
|
|
938
|
+
The tests would run against the previously generated procedure group, pass, and prove nothing
|
|
939
|
+
about the descriptors you just edited.
|
|
940
|
+
Run `voltro codegen` (or boot `voltro dev` once) and try again.
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
It refuses rather than regenerating because regenerating means importing your app's modules and config as a side effect of asking to run tests, and silently rewriting a checked-in source file is worse than stopping.
|
|
944
|
+
|
|
945
|
+
The check reads bytes only — no app module is imported — so it costs milliseconds. A generated file written by an older framework version carries no stamp and reads as stale; run `voltro codegen` once.
|
|
946
|
+
|
|
771
947
|
## `voltro agents-md`
|
|
772
948
|
|
|
773
949
|
```bash
|
|
@@ -797,8 +973,22 @@ For api apps, Voltro applies its own discovery walker on every save. The pattern
|
|
|
797
973
|
|
|
798
974
|
`*.tool.tsx` files are not discovered on their own — a tool is imported by the agent that uses it, so it's picked up through the agent file. Hidden dirs, `node_modules`, `dist`, and `.framework` are skipped.
|
|
799
975
|
|
|
976
|
+
Those names are matched as whole **path segments**, so a directory called `distribution/` or a file called `distTools.ts` is watched normally.
|
|
977
|
+
|
|
800
978
|
For web apps, Vite's built-in HMR handles the watch.
|
|
801
979
|
|
|
980
|
+
### Workspace packages are watched too
|
|
981
|
+
|
|
982
|
+
If your api depends on a workspace package (`"@acme/shared": "workspace:*"`), that package's `src/` is watched as well — editing `packages/shared/src/x.ts` restarts the api, exactly as editing a file inside the api would. The dependency set is resolved once at boot from the api's `package.json`, so adding a dependency needs a restart (it needs an install anyway).
|
|
983
|
+
|
|
984
|
+
Only real workspace packages are watched. A published npm dependency resolves inside `node_modules` and is skipped, so an app outside a monorepo watches nothing extra.
|
|
985
|
+
|
|
986
|
+
An edit in a dependency is logged with its package directory, not just the filename:
|
|
987
|
+
|
|
988
|
+
```
|
|
989
|
+
file changed — restarting file=shared/src/x.ts
|
|
990
|
+
```
|
|
991
|
+
|
|
802
992
|
## Restart triggers
|
|
803
993
|
|
|
804
994
|
API apps restart (full process kill) on a change to **any source file**
|
|
@@ -819,6 +1009,19 @@ The restart is a full re-exec — there is no in-process hot-reload of a
|
|
|
819
1009
|
handler body; editing a query's executor respawns the child (debounced
|
|
820
1010
|
80ms, so a burst of saves collapses into one restart).
|
|
821
1011
|
|
|
1012
|
+
### Reading the restart timing
|
|
1013
|
+
|
|
1014
|
+
The completion line is printed when the api **can serve a request** — not when the replacement process was spawned:
|
|
1015
|
+
|
|
1016
|
+
```text
|
|
1017
|
+
file changed — restarting file=notes.list.query.ts
|
|
1018
|
+
restart complete — api ready ms=1840
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
The first boot reports the same measurement as `dev server ready`. If a restart never prints its completion line, the child did not come up — look for the crash above it, which the supervisor logs before it goes back to waiting for the next save.
|
|
1022
|
+
|
|
1023
|
+
That number is the whole wait: the process start, the module graph, discovery, codegen, the store connection, the boot schema diff and plugin boot. It is the number to quote if the inner loop feels slow.
|
|
1024
|
+
|
|
822
1025
|
### How the old process is stopped
|
|
823
1026
|
|
|
824
1027
|
SIGTERM first. The child runs its teardown — plugin `onDeactivate`, the
|
|
@@ -892,6 +1095,32 @@ the env-file chain (app dir → ancestors) and hard-restarts, logging
|
|
|
892
1095
|
web closes Vite before the replacement spawns, so the restart can't hit a
|
|
893
1096
|
port-in-use race.
|
|
894
1097
|
|
|
1098
|
+
## Which port an app binds
|
|
1099
|
+
|
|
1100
|
+
Every command that starts an app listener — `voltro dev`, `voltro serve`,
|
|
1101
|
+
`voltro start`, `voltro dormancy` — resolves the port the same way, in this
|
|
1102
|
+
order:
|
|
1103
|
+
|
|
1104
|
+
1. `VOLTRO_DASHBOARD_PORT`, and only in the auto-launched dashboard process.
|
|
1105
|
+
2. `PORT` from the environment.
|
|
1106
|
+
3. `--port <n>` (`voltro serve`, `voltro dormancy`).
|
|
1107
|
+
4. `port:` in the app's `app.config.ts`.
|
|
1108
|
+
5. `4000` for an api app, `5173` for a web app.
|
|
1109
|
+
|
|
1110
|
+
`PORT` deliberately outranks `--port`: every host that assigns a port — a
|
|
1111
|
+
container platform, a PaaS, a Kubernetes Deployment — assigns it through `PORT`,
|
|
1112
|
+
and a `--port` baked into an image's start command must not override the port the
|
|
1113
|
+
host actually routed to.
|
|
1114
|
+
|
|
1115
|
+
A value that is not a port in `1..65535` (`PORT=`, `PORT=8080x`) is **ignored**
|
|
1116
|
+
with a warning naming the variable, and the next source wins. It is not passed to
|
|
1117
|
+
`listen()`: `Number('8080x')` is `NaN`, node reads that as "any free port", and
|
|
1118
|
+
the app would come up healthy at an address nobody can guess.
|
|
1119
|
+
|
|
1120
|
+
`voltro dev` does not take `--port`. Its file-watching supervisor respawns the
|
|
1121
|
+
app as `dev <app>` and drops flags, so a `--port` would silently stop applying at
|
|
1122
|
+
the first file change; set `PORT` for a one-off, or `port:` to keep it.
|
|
1123
|
+
|
|
895
1124
|
## Multiple instances on one machine
|
|
896
1125
|
|
|
897
1126
|
Run two api apps + two web apps in parallel? Every app reads the same `PORT` env, so prefer setting each app's `port:` in its own `app.config.ts` and `--cwd`-ing into each — that avoids one shared `PORT` clobbering them all. The dashboard auto-launches once on `:5179`; later instances see it's already up and skip it.
|
|
@@ -904,7 +1133,7 @@ voltro dev apps/acme/docs &
|
|
|
904
1133
|
voltro dev apps/orbit/web &
|
|
905
1134
|
```
|
|
906
1135
|
|
|
907
|
-
If you must override per-process from the shell, set `PORT` inline on each one (`PORT=4001 voltro dev apps/acme/api`) — but the config-file port is the cleaner path. `pnpm dev` at the
|
|
1136
|
+
If you must override per-process from the shell, set `PORT` inline on each one (`PORT=4001 voltro dev apps/acme/api`) — but the config-file port is the cleaner path. `pnpm dev` at the workspace root handles all of this for you.
|
|
908
1137
|
|
|
909
1138
|
## Anti-patterns
|
|
910
1139
|
|
|
@@ -999,7 +1228,7 @@ What it does:
|
|
|
999
1228
|
| Flag / env | Notes |
|
|
1000
1229
|
|---|---|
|
|
1001
1230
|
| `PORT=8080` | Override the listen port. |
|
|
1002
|
-
| `SSR_CACHE=postgres` | Use the Postgres-backed ISR cache. Requires the
|
|
1231
|
+
| `SSR_CACHE=postgres` | Use the Postgres-backed ISR cache. Requires the web process to also have a database in its environment — `DB_URL` (what the templates set), `DB_PRIMARY_URL`, `DB_HOST` or `PG_HOST`. Without one, `voltro start` **aborts** on `NODE_ENV=production`/`staging` and warns loudly elsewhere; it no longer falls back to the in-memory cache in silence. Default is in-memory. |
|
|
1003
1232
|
| `VOLTRO_INSPECT=off` | Disable the inspect HTTP endpoints in production. |
|
|
1004
1233
|
| `VOLTRO_INSPECT_TOKEN=…` | Bearer token guard on the inspect endpoints. |
|
|
1005
1234
|
|
|
@@ -1026,12 +1255,23 @@ The response includes a `x-voltro-rendered-by` header (`prerender` / `ssr` / `is
|
|
|
1026
1255
|
|
|
1027
1256
|
```bash
|
|
1028
1257
|
SSR_CACHE=memory voltro start # default — per-process, doesn't survive restart
|
|
1029
|
-
# Postgres-backed cache — needs
|
|
1258
|
+
# Postgres-backed cache — the web process needs a database in its env. Any of
|
|
1259
|
+
# the usual variables works; DB_URL is what the templates set.
|
|
1260
|
+
SSR_CACHE=postgres DB_URL=postgres://… voltro start
|
|
1030
1261
|
SSR_CACHE=postgres PG_HOST=… PG_PORT=… PG_USER=… PG_PASSWORD=… PG_DATABASE=… voltro start
|
|
1031
1262
|
```
|
|
1032
1263
|
|
|
1033
1264
|
For multi-instance + horizontal scale → Postgres. The cache table is auto-created on first boot.
|
|
1034
1265
|
|
|
1266
|
+
> **This used to recognise `PG_HOST` and nothing else.** An app configured the
|
|
1267
|
+
> documented way — `SSR_CACHE=postgres` plus `DB_URL` — silently got the
|
|
1268
|
+
> per-process memory cache, announced as `isr cache backend: memory
|
|
1269
|
+
> (per-process)`: an info line that reads like the default rather than like a
|
|
1270
|
+
> refusal. Both the cache and the CDC invalidator go through the same connection
|
|
1271
|
+
> resolver as everything else now, so `DB_URL` / `DB_PRIMARY_URL` / `DB_HOST` /
|
|
1272
|
+
> `PG_HOST` all work — and `PG_SSL` comes with them. Asking for the postgres
|
|
1273
|
+
> cache and getting memory is now a boot failure in production, not a log line.
|
|
1274
|
+
|
|
1035
1275
|
## Tenant-aware ISR
|
|
1036
1276
|
|
|
1037
1277
|
Pages with `tenantAware: true` get separate cache entries per tenant. The cache key becomes `<pathname>|tenant=<tenantId>`. See [Render modes](/docs/routing/render-modes).
|
|
@@ -1040,6 +1280,12 @@ Pages with `tenantAware: true` get separate cache entries per tenant. The cache
|
|
|
1040
1280
|
|
|
1041
1281
|
For pages with `cacheInvalidatesOn: ['table', …]`, `voltro start` reads Postgres logical replication. Writes to listed tables invalidate every matching cache entry. Requires `SSR_CACHE=postgres` + `wal_level=logical`.
|
|
1042
1282
|
|
|
1283
|
+
If routes declare `cacheInvalidatesOn` and the web process has no database in
|
|
1284
|
+
its environment, boot now WARNS and names those routes — they fall back to plain
|
|
1285
|
+
`revalidate` staleness. That gap used to be reported at `debug`, which is
|
|
1286
|
+
invisible at the default level and indistinguishable from live invalidation
|
|
1287
|
+
working.
|
|
1288
|
+
|
|
1043
1289
|
## Graceful shutdown
|
|
1044
1290
|
|
|
1045
1291
|
`voltro start` handles SIGTERM:
|
|
@@ -1115,6 +1361,35 @@ Docker step) and prints the exact remedy: add a `voltro build .` step before
|
|
|
1115
1361
|
`voltro serve .`. Drop it into your image build right after `voltro build` to
|
|
1116
1362
|
guarantee the artefact is present before the image ships.
|
|
1117
1363
|
|
|
1364
|
+
### The access-decision gate
|
|
1365
|
+
|
|
1366
|
+
Before the authz scan below — which is a heuristic over executor SOURCE — doctor
|
|
1367
|
+
runs the same **gate the boot runs**: every wire-exposed procedure must declare
|
|
1368
|
+
`guards:` or `openAccess:`. It is not advisory and not a ratchet, because a green
|
|
1369
|
+
answer here means the app starts:
|
|
1370
|
+
|
|
1371
|
+
```
|
|
1372
|
+
access decisions · security.defaultDeny ON
|
|
1373
|
+
✗ no access decision 3
|
|
1374
|
+
✓ openAccess, declared on purpose 2
|
|
1375
|
+
pricing.current — public pricing page, reads no caller data
|
|
1376
|
+
status.ping — health probe
|
|
1377
|
+
|
|
1378
|
+
✗ invoices.list (query)
|
|
1379
|
+
src/api/invoices.query.ts
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
Every undecided procedure is listed — never a prefix — and the same set is in
|
|
1383
|
+
`voltro doctor --json` under `accessDecisions` for a CI gate:
|
|
1384
|
+
|
|
1385
|
+
```bash
|
|
1386
|
+
voltro doctor --json | jq '.accessDecisions.undecided[] | {tag, kind, file}'
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
An app that sets `security: { defaultDeny: false }` still gets the list, marked
|
|
1390
|
+
advisory, and doctor does not fail on it. Detail:
|
|
1391
|
+
[Authorization](/docs/authentication/authorization).
|
|
1392
|
+
|
|
1118
1393
|
### The authz scan
|
|
1119
1394
|
|
|
1120
1395
|
`voltro doctor` answers one mechanical question over every executor: **does it
|
|
@@ -1612,6 +1887,13 @@ voltro migrate apps/api # explicit app directory (defaults to cwd)
|
|
|
1612
1887
|
|
|
1613
1888
|
`voltro migrate` forwards its arguments to `voltro db apply`, so the same flags apply; `--create-only` selects the bootstrap-only emitter instead. The diff / plan / apply / drift / squash workflow lives under `voltro db` (see below).
|
|
1614
1889
|
|
|
1890
|
+
`--create-only` creates the SAME set of tables every other command declares: your
|
|
1891
|
+
entities, the feature-mix framework tables, the agent-thread tables when the app
|
|
1892
|
+
has an `*.agent.tsx`, and every plugin's `extendSchema.tables` — plus each
|
|
1893
|
+
plugin's `extendSchema.migrations` afterwards. Before 0.34.0 it assembled that
|
|
1894
|
+
set itself and got the last two wrong, so bootstrapping a fresh database the
|
|
1895
|
+
documented way produced one with no plugin tables at all.
|
|
1896
|
+
|
|
1615
1897
|
For the deep dive on the schema DSL + day-to-day patterns, see [Database / Migrations](/docs/database/migrations).
|
|
1616
1898
|
|
|
1617
1899
|
## How discovery works
|
|
@@ -1654,14 +1936,28 @@ voltro db plan # diff declared schema vs live, color-
|
|
|
1654
1936
|
voltro db plan --json # machine-readable for CI / PR comments
|
|
1655
1937
|
voltro db plan --sql # raw DDL preview
|
|
1656
1938
|
voltro db plan --against <url> # diff vs a REMOTE env via /_voltro/inspect/migrations
|
|
1657
|
-
voltro db apply # execute the plan (
|
|
1939
|
+
voltro db apply # execute the plan (refuses NODE_ENV=production — and an UNSET NODE_ENV resolves to production)
|
|
1658
1940
|
voltro db apply --plan plan.json # prod: apply a pre-reviewed plan from CI/CD
|
|
1659
1941
|
voltro db plans [--limit 20] # plan history from _voltro_migration_plans
|
|
1942
|
+
voltro db branch --pr <n> # REHEARSE the plan on a throwaway branch of the live schema
|
|
1660
1943
|
voltro db drift # alert if live diverged from the latest applied fingerprint
|
|
1661
1944
|
voltro db squash --before <iso-date> # consolidate history into one snapshot
|
|
1662
1945
|
voltro db restore-snapshot <plan-id> # restore soft-dropped columns from a plan
|
|
1663
1946
|
```
|
|
1664
1947
|
|
|
1948
|
+
> **Every `voltro db …` / `voltro migrate` invocation declares its
|
|
1949
|
+
> environment.** An unset `NODE_ENV` resolves to `production` for these
|
|
1950
|
+
> commands — the same way it does for `voltro serve` and `voltro start` — so a
|
|
1951
|
+
> bare `voltro db apply` with no `NODE_ENV` refuses (exit 3) rather than
|
|
1952
|
+
> applying an un-reviewed diff. Locally: `NODE_ENV=development voltro db apply`,
|
|
1953
|
+
> or put `NODE_ENV=development` in your `.env` (a declared value always wins).
|
|
1954
|
+
> `voltro dev` declares `development` for itself and needs nothing.
|
|
1955
|
+
>
|
|
1956
|
+
> The second reason it matters is not the refusal: `_voltro_traces` and
|
|
1957
|
+
> `_voltro_undo_log` are created only outside production, so a migration command
|
|
1958
|
+
> that resolved the environment differently from the serving process **declared
|
|
1959
|
+
> a different schema** — and the declared set is what the fingerprint hashes.
|
|
1960
|
+
|
|
1665
1961
|
A separate file-based migration surface (the offline escape hatch) lives alongside it:
|
|
1666
1962
|
|
|
1667
1963
|
```bash
|
|
@@ -1692,9 +1988,26 @@ For zero-downtime deploys with breaking schema changes:
|
|
|
1692
1988
|
|
|
1693
1989
|
The framework doesn't enforce these — that's an SRE responsibility. See [Migrations](/docs/database/migrations) for the playbook.
|
|
1694
1990
|
|
|
1991
|
+
## Rehearsing a migration before it reaches production — `voltro db branch`
|
|
1992
|
+
|
|
1993
|
+
`voltro db plan` tells you what the diff IS. `voltro db branch` tells you what it
|
|
1994
|
+
DOES: it branches the live schema into a throwaway namespace, applies the plan
|
|
1995
|
+
there (destructive operations included — the branch is disposable, so the
|
|
1996
|
+
operation most likely to fail is the one that actually gets rehearsed), re-plans
|
|
1997
|
+
to prove the migration converges, and drops the branch.
|
|
1998
|
+
|
|
1999
|
+
```bash
|
|
2000
|
+
voltro db branch --pr 128 --json > rehearsal.json # exit 2 = the plan destroys data
|
|
2001
|
+
```
|
|
2002
|
+
|
|
2003
|
+
Postgres only, and it says so on the other dialects rather than emitting Postgres
|
|
2004
|
+
syntax at them. Full behaviour, exit codes and the `--seed` trade-off:
|
|
2005
|
+
[Data branching](/docs/database/branching).
|
|
2006
|
+
|
|
1695
2007
|
## See also
|
|
1696
2008
|
|
|
1697
2009
|
- [Database / Migrations](/docs/database/migrations) — the schema DSL deep dive
|
|
2010
|
+
- [Data branching](/docs/database/branching) — `voltro db branch` + the branch primitive
|
|
1698
2011
|
- [Self-hosting](/docs/deployment/self-hosting) — production migration patterns
|
|
1699
2012
|
|
|
1700
2013
|
|
|
@@ -1794,6 +2107,45 @@ voltro check --url https://api.example.com
|
|
|
1794
2107
|
|
|
1795
2108
|
`--token` (or `VOLTRO_INSPECT_TOKEN`) supplies the bearer; `VOLTRO_INSPECT_URL` sets a default target so you can drop the flag. Works for `inspect`, `logs`, `traces`, `workflows`, `cluster` and `check`.
|
|
1796
2109
|
|
|
2110
|
+
## `voltro probe access` — is a declared guard actually enforced?
|
|
2111
|
+
|
|
2112
|
+
`voltro check` reads an app's manifest and reports a procedure with **no access
|
|
2113
|
+
decision**. It cannot tell you whether the decisions that ARE declared are
|
|
2114
|
+
enforced. `voltro probe access` asks the running app:
|
|
2115
|
+
|
|
2116
|
+
```bash
|
|
2117
|
+
voltro probe access # every live api
|
|
2118
|
+
voltro probe access --url https://api.example.com --strict
|
|
2119
|
+
```
|
|
2120
|
+
|
|
2121
|
+
It calls every procedure that declares a guard with **no credentials at all**
|
|
2122
|
+
and reports any that answer anyway:
|
|
2123
|
+
|
|
2124
|
+
```
|
|
2125
|
+
api: probed 14 guarded procedure(s) with NO credentials
|
|
2126
|
+
✗ orders.export — ANSWERED an unauthenticated call
|
|
2127
|
+
? billing.invoice — answered 'ParseError' — not an access refusal, so the guard was not reached
|
|
2128
|
+
✓ 12 refused · 1 inconclusive · 1 admitted
|
|
2129
|
+
```
|
|
2130
|
+
|
|
2131
|
+
Three verdicts, and the third is what keeps the command honest:
|
|
2132
|
+
|
|
2133
|
+
| Verdict | Meaning |
|
|
2134
|
+
|---|---|
|
|
2135
|
+
| `refused` | The call came back as an access refusal. The declaration is enforced. |
|
|
2136
|
+
| `admitted` | The call SUCCEEDED without credentials. **This is the finding.** |
|
|
2137
|
+
| `inconclusive` | The call failed for a reason that is not an access refusal — usually payload validation running before the guard. **Not a pass.** |
|
|
2138
|
+
|
|
2139
|
+
Exit code is non-zero on any `admitted`. `--strict` also fails on
|
|
2140
|
+
`inconclusive`, which is what you want in CI: "I could not tell" should block.
|
|
2141
|
+
|
|
2142
|
+
**What it deliberately does not do.** It probes ANONYMOUSLY, so it cannot tell
|
|
2143
|
+
`orders:read` from `orders:write` — it answers exactly one question, and the
|
|
2144
|
+
alternative (minting a subject per guard) would put credential minting into a
|
|
2145
|
+
command that can be pointed at production. Procedures declared `openAccess:` are
|
|
2146
|
+
skipped; probing them would report every deliberately-public route as a finding
|
|
2147
|
+
and bury the real ones.
|
|
2148
|
+
|
|
1797
2149
|
## Securing the local surface
|
|
1798
2150
|
|
|
1799
2151
|
`voltro dev` mints a per-project `VOLTRO_INSPECT_TOKEN` into `.env.local`, so the inspect surface is authenticated from the first boot — the dev server listens on every interface, and without a token anyone on the same network could read your rows, schema and logs. You don't have to wire it anywhere: the CLI picks the token up from the runtime registry (so the commands work from any directory), and the dashboard's server-side proxy supplies it for same-machine targets. Setting `VOLTRO_INSPECT_TOKEN` yourself always wins.
|
|
@@ -1837,7 +2189,16 @@ curl -s localhost:$PORT/_voltro/inspect/rpc | jq # api: every query / mut
|
|
|
1837
2189
|
curl -s localhost:$PORT/_voltro/inspect/metrics | jq # rolling per-tag latency + invocation count
|
|
1838
2190
|
```
|
|
1839
2191
|
|
|
1840
|
-
There is no `/_voltro/inspect/queries` endpoint. The registered GET surface is `app`, `routes` (web-only), `cache` (web-only), `rpc` (api-only), `metrics`, and `
|
|
2192
|
+
There is no `/_voltro/inspect/queries` endpoint. The registered GET surface is `app`, `routes` (web-only), `cache` (web-only), `rpc` (api-only), `metrics`, `subscriptions` (api-only), `checks` (api-only) and `agent/tools` (api-only) — `rpc` is the procedure list, `routes` is the web page tree.
|
|
2193
|
+
|
|
2194
|
+
### Invariant checks + the agent-tool surface
|
|
2195
|
+
|
|
2196
|
+
```bash
|
|
2197
|
+
curl -s localhost:$PORT/_voltro/inspect/checks | jq # browser-safety, procedure-access, convergence, serverOnly
|
|
2198
|
+
curl -s localhost:$PORT/_voltro/inspect/agent/tools | jq # the exposeAsTool procedures an agent may run
|
|
2199
|
+
```
|
|
2200
|
+
|
|
2201
|
+
`checks` runs the framework's own invariant checks and answers `pass | fail | unavailable` per check — `unavailable` means THIS process cannot answer it (a deployed `voltro serve` has no source tree to walk) and is never a pass. `agent/tools` lists the policy-admitted agent tools, and its sibling `POST /_voltro/inspect/agent/call` executes one; both are off until `agents: { mcp: true }`, and the call additionally needs the write credential plus an app credential on `x-voltro-agent-authorization`. See the [MCP server](/docs/cli/mcp) page for the full gate list.
|
|
1841
2202
|
|
|
1842
2203
|
What the surface reads:
|
|
1843
2204
|
|
|
@@ -1988,6 +2349,19 @@ voltro test --coverage --reporter=junit --outputFile=reports/junit.xml
|
|
|
1988
2349
|
That covers coverage numbers and a JUnit report for a merge-request widget, which
|
|
1989
2350
|
is what most pipelines want beyond the exit code.
|
|
1990
2351
|
|
|
2352
|
+
**`--coverage` needs a provider package.** vitest ships coverage providers as
|
|
2353
|
+
*optional* peer dependencies, so nothing installs one for you. Every app
|
|
2354
|
+
scaffolded by `voltro create-project` / `voltro add-app` already declares
|
|
2355
|
+
`@vitest/coverage-v8` beside vitest; an older project adds it once:
|
|
2356
|
+
|
|
2357
|
+
```bash
|
|
2358
|
+
pnpm add -D @vitest/coverage-v8 # or --coverage.provider=istanbul → @vitest/coverage-istanbul
|
|
2359
|
+
```
|
|
2360
|
+
|
|
2361
|
+
`voltro test` checks for it *before* booting vitest and refuses with that
|
|
2362
|
+
install command, because vitest's own failure (`Cannot find dependency
|
|
2363
|
+
'@vitest/coverage-v8'`) names neither the flag nor the fix.
|
|
2364
|
+
|
|
1991
2365
|
The framework keeps three decisions for itself and they win over a forwarded
|
|
1992
2366
|
flag: the **root** (a positional that is an existing directory, which vitest
|
|
1993
2367
|
would otherwise read as a filter), `--watch`, and `passWithNoTests` — an explicit
|
|
@@ -2015,25 +2389,68 @@ voltro e2e apps/web # explicit web app directory
|
|
|
2015
2389
|
|
|
2016
2390
|
1. `voltro dev` for the api app.
|
|
2017
2391
|
2. `voltro dev` for the web app.
|
|
2018
|
-
3.
|
|
2392
|
+
3. Runs every file matching `e2e/**/*.spec.ts`, one process each, as a **plain tsx script** (`node --import tsx <file>`).
|
|
2019
2393
|
4. Tear down: stops the boot processes.
|
|
2020
2394
|
|
|
2021
|
-
The e2e
|
|
2395
|
+
**There is no test runner and no browser driver here.** A spec is an ordinary TypeScript program: it runs top to bottom, and a non-zero exit code (an uncaught throw, `process.exit(1)`, a failed `node:assert`) is a failed file. The framework ships no `describe`/`it`, no `page` fixture, no reporter, no sharding, and no browser — because what `voltro e2e` actually contributes is the *lifecycle*, and the lifecycle is the same whichever driver you pick.
|
|
2396
|
+
|
|
2397
|
+
Two environment variables are handed to every spec:
|
|
2398
|
+
|
|
2399
|
+
| Variable | Value |
|
|
2400
|
+
|---|---|
|
|
2401
|
+
| `WEB_URL` | `http://localhost:<webPort>` — the booted web app |
|
|
2402
|
+
| `API_URL` | `http://localhost:<apiPort>` — the booted api app |
|
|
2403
|
+
|
|
2404
|
+
A spec that only needs the API is just `fetch` plus `node:assert`:
|
|
2022
2405
|
|
|
2023
2406
|
```ts
|
|
2024
|
-
// apps/web/
|
|
2025
|
-
import
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
await page.click('button[type=submit]')
|
|
2032
|
-
await expect(page).toHaveURL(/\/dashboard/)
|
|
2407
|
+
// apps/web/e2e/signup.spec.ts
|
|
2408
|
+
import assert from 'node:assert/strict'
|
|
2409
|
+
|
|
2410
|
+
const res = await fetch(`${process.env.API_URL}/v1/signup`, {
|
|
2411
|
+
method: 'POST',
|
|
2412
|
+
headers: { 'content-type': 'application/json' },
|
|
2413
|
+
body: JSON.stringify({ email: 'a@b.com', password: 'correct horse battery staple' }),
|
|
2033
2414
|
})
|
|
2415
|
+
|
|
2416
|
+
assert.equal(res.status, 200)
|
|
2417
|
+
console.log('✓ signup accepted')
|
|
2034
2418
|
```
|
|
2035
2419
|
|
|
2036
|
-
|
|
2420
|
+
To drive a real browser, bring your own driver and launch it inside the spec — the framework does not choose one for you, and does not install one:
|
|
2421
|
+
|
|
2422
|
+
```ts
|
|
2423
|
+
// apps/web/e2e/signup-browser.spec.ts
|
|
2424
|
+
import assert from 'node:assert/strict'
|
|
2425
|
+
import { chromium } from 'playwright-core' // your dependency, not the framework's
|
|
2426
|
+
|
|
2427
|
+
const browser = await chromium.launch()
|
|
2428
|
+
const page = await browser.newPage()
|
|
2429
|
+
await page.goto(`${process.env.WEB_URL}/signup`)
|
|
2430
|
+
await page.fill('[name=email]', 'a@b.com')
|
|
2431
|
+
await page.fill('[name=password]', 'correct horse battery staple')
|
|
2432
|
+
await page.click('button[type=submit]')
|
|
2433
|
+
await page.waitForURL(/\/dashboard/)
|
|
2434
|
+
assert.ok(page.url().includes('/dashboard'))
|
|
2435
|
+
await browser.close()
|
|
2436
|
+
```
|
|
2437
|
+
|
|
2438
|
+
Configure the boot in `app.config.ts`:
|
|
2439
|
+
|
|
2440
|
+
```ts
|
|
2441
|
+
export default {
|
|
2442
|
+
type: 'web' as const,
|
|
2443
|
+
name: 'web',
|
|
2444
|
+
e2e: {
|
|
2445
|
+
apiDir: '../api', // relative to the web app root
|
|
2446
|
+
pattern: 'e2e/**/*.spec.ts',
|
|
2447
|
+
apiPort: 4000,
|
|
2448
|
+
webPort: 5173,
|
|
2449
|
+
},
|
|
2450
|
+
}
|
|
2451
|
+
```
|
|
2452
|
+
|
|
2453
|
+
Anything below the browser — a handler, a guard, a REST route, an `Idempotency-Key` replay, the `x-tenant` header — is faster and more precise from a [request-level test](/docs/testing/unit-testing#request-level-testing-maketestapp), which needs no booted process at all. Reach for `voltro e2e` when the thing under test *is* the two processes talking to each other.
|
|
2037
2454
|
|
|
2038
2455
|
## Securing the inspect surface
|
|
2039
2456
|
|
|
@@ -2186,6 +2603,26 @@ voltro data import ./out --force # import despite schema drift AND c
|
|
|
2186
2603
|
|
|
2187
2604
|
Import is **integrity-checked** (each table's checksum + row count verified as it decodes; each asset re-hashed against its content address), applies tables in **FK-parent-first order**, and a resumed run skips already-applied tables via the ledger.
|
|
2188
2605
|
|
|
2606
|
+
### Postgres targets bulk-load via COPY
|
|
2607
|
+
|
|
2608
|
+
On a **postgres** target, the direct import switches to `COPY … FROM STDIN`
|
|
2609
|
+
wherever plain-INSERT semantics provably hold: `--mode replace` (the tables were
|
|
2610
|
+
just truncated) and the default `upsert` into a table that is **empty** at
|
|
2611
|
+
import time — the fresh-target shape every cross-dialect migration
|
|
2612
|
+
(mysql → postgres, sqlite → postgres, …) lands in. Measured on a 7-column table
|
|
2613
|
+
(text/int/bool/jsonb/timestamptz) with 50 000 rows against a local postgres:
|
|
2614
|
+
row-by-row **12.8 s (~3.9 k rows/s)** vs COPY **0.59 s (~84.6 k rows/s)** —
|
|
2615
|
+
**21.7× faster**. Your factor depends on row width and network latency;
|
|
2616
|
+
COPY's advantage grows with per-row round-trip cost.
|
|
2617
|
+
|
|
2618
|
+
Everything else keeps the per-row `DataStore` writes: `upsert` into a non-empty
|
|
2619
|
+
table (COPY cannot upsert), `append` (per-row conflict handling), `--atomic`
|
|
2620
|
+
(the COPY connection would sit outside the transaction), and every other
|
|
2621
|
+
dialect. A refused COPY batch (an FK the deferred pass repairs later, a value
|
|
2622
|
+
COPY text can't carry) is atomic — nothing landed — so the importer replays
|
|
2623
|
+
exactly that batch through the per-row path and continues; semantics are
|
|
2624
|
+
identical, only the speed differs.
|
|
2625
|
+
|
|
2189
2626
|
### Schema-drift pre-flight
|
|
2190
2627
|
|
|
2191
2628
|
Every bundle records the **schema fingerprint** of the source it was exported from (a stable hash of the schema shape — the same fingerprint prod boot uses to detect drift). Before touching the target, `import` **recomputes the target app's fingerprint the same way** and compares it to the bundle's. On a mismatch it **refuses, fail-closed, before any row lands** — a drifted target (a missing column, a renamed table) would otherwise fail mid-load with a raw database error after some rows already committed:
|
|
@@ -2606,20 +3043,22 @@ flag is the one-off override.
|
|
|
2606
3043
|
<!-- source: en/cli/mcp.md -->
|
|
2607
3044
|
## MCP server (voltro-mcp)
|
|
2608
3045
|
|
|
2609
|
-
_Wire a running Voltro api into Claude Code / Cursor as an MCP server — read
|
|
3046
|
+
_Wire a running Voltro api into Claude Code / Cursor as an MCP server — read the app's procedures, tables, workflows and JSON Schemas, EXECUTE the procedures you expose as agent tools under your app's own permissions, and run the framework's invariant checks. Over stdio or Streamable HTTP._
|
|
2610
3047
|
|
|
2611
3048
|
`@voltro/mcp` ships two standalone bins — **`voltro-mcp`** (stdio) and **`voltro-mcp-http`** (Streamable HTTP) — that serve a running api's capability manifest to a coding agent over the Model Context Protocol. The agent can then discover what the backend exposes — every rpc procedure with its input/output JSON Schema, the user tables, the workflows, the schema-driven-UI widget kinds — before writing UI or agent code.
|
|
2612
3049
|
|
|
2613
|
-
|
|
3050
|
+
Discovery is **read-only metadata** and is what you get with nothing configured beyond a URL: the bins talk to the same `GET /_voltro/inspect/manifest` endpoint the [inspect surface](/docs/cli/inspect) exposes, and honour its token gate. Two further surfaces are **off until you turn them on** — executing your app's agent tools, and the invariant checks. Both are covered below. The server advertises three MCP capabilities — **tools**, **resources**, and **prompts**.
|
|
2614
3051
|
|
|
2615
3052
|
## Setup
|
|
2616
3053
|
|
|
2617
|
-
|
|
3054
|
+
Four environment variables. The first two cover read-only discovery; the last two are what an executing tool call needs.
|
|
2618
3055
|
|
|
2619
3056
|
| Var | Default | Notes |
|
|
2620
3057
|
|---|---|---|
|
|
2621
3058
|
| `VOLTRO_INSPECT_URL` | `http://localhost:4000` | Base URL of the running api. |
|
|
2622
|
-
| `VOLTRO_INSPECT_TOKEN` | _(unset)_ | Sent as `Authorization: Bearer <token
|
|
3059
|
+
| `VOLTRO_INSPECT_TOKEN` | _(unset)_ | Sent as `Authorization: Bearer <token>`. The inspect surface is fail-closed, so without it every call is a 401. |
|
|
3060
|
+
| `VOLTRO_INSPECT_WRITE_TOKEN` | _(unset)_ | Required to EXECUTE an app tool — a tool call is a non-GET inspect request, and those need a second credential. Read-only discovery does not use it. |
|
|
3061
|
+
| `VOLTRO_AGENT_TOKEN` | _(unset)_ | The **app** credential a tool call executes as. Never the inspect token — see below. |
|
|
2623
3062
|
|
|
2624
3063
|
### Claude Code
|
|
2625
3064
|
|
|
@@ -2654,6 +3093,9 @@ claude mcp add voltro --env VOLTRO_INSPECT_URL=http://localhost:4001 -- npx -y @
|
|
|
2654
3093
|
| `voltro_get_table` | One table's full column list (types, nullability, FK targets, enums). |
|
|
2655
3094
|
| `voltro_list_workflows` | The registered durable workflows. |
|
|
2656
3095
|
| `voltro_list_widgets` | The schema-driven-UI widget kinds. |
|
|
3096
|
+
| `voltro_check_invariants` | The framework's invariant checks against the running app — see below. |
|
|
3097
|
+
|
|
3098
|
+
Plus one `app_<procedure>` tool per procedure your app exposes as an agent tool AND its policy admits — see the next section. Those are the only tools that execute anything.
|
|
2657
3099
|
|
|
2658
3100
|
## The resources
|
|
2659
3101
|
|
|
@@ -2675,6 +3117,94 @@ Reusable MCP **prompt templates** that render against the _live_ manifest, so th
|
|
|
2675
3117
|
| `explain_table` | `table` | The table's schema + the procedures that read/write it. |
|
|
2676
3118
|
| `wire_ui_for_procedure` | `tag` | A brief to call one procedure and render its result, embedding its real input/output schema. |
|
|
2677
3119
|
|
|
3120
|
+
## Executing your app's procedures
|
|
3121
|
+
|
|
3122
|
+
A procedure annotated `exposeAsTool` can be **called** by the agent — the tool body is the real rpc handler, run under a `Subject` your app's own auth chain resolved. So the agent's ceiling is that subject's permissions, by construction: there is no second authorization path, because there is no second path. A guard that refuses the subject refuses the agent.
|
|
3123
|
+
|
|
3124
|
+
It is off until you say otherwise, at five independent gates:
|
|
3125
|
+
|
|
3126
|
+
```ts
|
|
3127
|
+
// app.config.ts
|
|
3128
|
+
export default {
|
|
3129
|
+
agents: {
|
|
3130
|
+
tools: { allow: ['todos.*'], deny: ['*.purge'], includeWrites: true },
|
|
3131
|
+
mcp: true,
|
|
3132
|
+
},
|
|
3133
|
+
}
|
|
3134
|
+
```
|
|
3135
|
+
|
|
3136
|
+
```ts
|
|
3137
|
+
// mutations/todos.create.mutation.ts
|
|
3138
|
+
export const descriptor = defineMutation({
|
|
3139
|
+
name: 'todos.create',
|
|
3140
|
+
input: Schema.Struct({ title: Schema.String }),
|
|
3141
|
+
guards: [{ scope: 'todos:write' }],
|
|
3142
|
+
exposeAsTool: { description: 'Create a todo for the signed-in user', confirm: false },
|
|
3143
|
+
})
|
|
3144
|
+
```
|
|
3145
|
+
|
|
3146
|
+
1. **`agents.mcp: true`.** Not implied by anything else. Having an inspect token is not consent to let an agent execute procedures.
|
|
3147
|
+
2. **`VOLTRO_INSPECT_TOKEN`** — the transport is fail-closed; `voltro dev` mints one per project, `voltro serve` mints nothing.
|
|
3148
|
+
3. **`VOLTRO_INSPECT_WRITE_TOKEN`** + the `x-voltro-inspect-write` header. A tool call is a POST, and every non-GET inspect request already needed a second credential. An existing deployment with only the read token therefore executes nothing.
|
|
3149
|
+
4. **`VOLTRO_AGENT_TOKEN`** — the app credential the call executes AS, sent on its own `x-voltro-agent-authorization` header. **Required.** The inspect bearer is an operator credential; letting it double as an app identity would be exactly the second authorization path, and running as the anonymous subject instead would execute under a principal nobody chose. With [`apiKeys: true`](/docs/configuration/api-keys) your app already mints a scoped credential for this — scope it to what the agent may do, not to what you may do.
|
|
3150
|
+
5. **`agents.tools`** — the same `AppToolPolicy` the in-process [`appTools`](/docs/ai/tools) loop takes, so one policy covers both. `deny` beats `allow`; `includeWrites: true` is required before any mutation or action is callable at all.
|
|
3151
|
+
|
|
3152
|
+
Then the app's own guards run. Nothing above replaces them.
|
|
3153
|
+
|
|
3154
|
+
### `confirm` tools are not mounted here
|
|
3155
|
+
|
|
3156
|
+
`confirm` means a human approves the concrete call before it executes. There is no human in the MCP server's process, and there is no way to produce one: a confirmation carried in the tool's arguments is written by the model, and an MCP client's approval prompt is a property of that client — several harnesses auto-approve. So a `confirm` tool is dropped, with that reason, rather than mounted in the hope that the far side asks.
|
|
3157
|
+
|
|
3158
|
+
Writes confirm by default. An app that wants one callable unattended says so per descriptor (`exposeAsTool: { confirm: false }`) or app-wide (`agents.tools.requireConfirmForWrites: false`) — both are edits a reviewer sees in the diff.
|
|
3159
|
+
|
|
3160
|
+
### Naming, and what an agent sees
|
|
3161
|
+
|
|
3162
|
+
`todos.create` mounts as `app_todos_create` (MCP tool names are `[A-Za-z0-9_-]`). The tag is resolved back through the listing the server rendered, never by un-mangling the name the model produced, so no amount of argument shaping selects a different procedure. Writes are marked `[WRITE]` in the description — the model has no other signal that one of two tools destroys data.
|
|
3163
|
+
|
|
3164
|
+
Everything that did NOT mount is reported with a reason (`GET /_voltro/inspect/agent/tools` returns `dropped[]`), because a tool silently missing from an agent's set is a support ticket that opens with "the agent says it can't do that".
|
|
3165
|
+
|
|
3166
|
+
### In the audit trail
|
|
3167
|
+
|
|
3168
|
+
An agent call runs the same plugin interceptor chain as a socket call, so `plugin-audit` records it as usual. It additionally stamps `via: 'agent'` on the write attribution, with the subject id of the **person** the agent acted as — an agent never escalates identity, which is precisely why an unmarked agent write would be indistinguishable from a human one. A change-event tap reads it as `event.via`.
|
|
3169
|
+
|
|
3170
|
+
### What this does NOT defend against
|
|
3171
|
+
|
|
3172
|
+
Stated rather than implied, because a bound you assume is worse than one you do not have:
|
|
3173
|
+
|
|
3174
|
+
- **Prompt injection that steers the model into misusing a tool it IS permitted to run.** The allowlist bounds WHICH tools exist; it cannot bound intent. Tool results are your app's data, and app data can contain instructions.
|
|
3175
|
+
- **A client holding all three credentials.** It can call any admitted tool with any arguments. The bound is the subject's permissions — which is the design, and the reason to scope `VOLTRO_AGENT_TOKEN` narrowly.
|
|
3176
|
+
- **Call rate.** `maxPerRun` is reported for a client to honour; honouring it is the client's. Use [`plugin-ratelimit`](/docs/plugins/ratelimit) on the procedure for a bound that holds regardless of who is calling.
|
|
3177
|
+
|
|
3178
|
+
## Verifying your own work
|
|
3179
|
+
|
|
3180
|
+
`voltro_check_invariants` runs the framework's own invariant checks against the **running** app and returns a machine-readable verdict — the loop that turns "I generated some code" into "I checked it". `GET /_voltro/inspect/checks` is the same thing over HTTP.
|
|
3181
|
+
|
|
3182
|
+
| Check | Answers |
|
|
3183
|
+
|---|---|
|
|
3184
|
+
| `browser-safety` | Does the generated rpcGroup transitively value-import a server-only module? The finding carries the full **import chain** — a bare specifier says a rule broke, the chain says which shared `lib/` file broke it. |
|
|
3185
|
+
| `procedure-access` | Does every wire-exposed procedure declare `guards:` or `openAccess:`? Runs the same verdict the boot gate runs, including `security.defaultDeny`. |
|
|
3186
|
+
| `schema-convergence` | Has the live schema drifted, and are operations pending? Read from the same snapshot `voltro db plan --against` reads. |
|
|
3187
|
+
| `server-only-exposure` | Does a wire-reachable query declare a `.serverOnly()` column in its output? |
|
|
3188
|
+
|
|
3189
|
+
Each answers `pass`, `fail`, or **`unavailable`** — and `unavailable` is never a pass. Two of these read the source tree, and a deployed `voltro serve` has no generated rpcGroup to walk (frequently no `src/` at all after a `pnpm deploy`), so it reports them `unavailable` **with the reason** rather than omitting them. Three green checks that you cannot distinguish from "nobody looked" would be worse than no answer.
|
|
3190
|
+
|
|
3191
|
+
```json
|
|
3192
|
+
{
|
|
3193
|
+
"checks": [
|
|
3194
|
+
{ "id": "browser-safety", "status": "unavailable",
|
|
3195
|
+
"reason": "this process has no generated rpcGroup to walk — the check reads the SOURCE import graph…" },
|
|
3196
|
+
{ "id": "procedure-access", "status": "fail",
|
|
3197
|
+
"summary": "1 wire-exposed procedure(s) declare no access decision",
|
|
3198
|
+
"findings": [{ "tag": "todos.secret", "kind": "query", "file": "queries/todos.secret.query.ts" }],
|
|
3199
|
+
"fix": "give each one either `guards: [{ scope: '…' }]` or `openAccess: '<why it is public>'`…" }
|
|
3200
|
+
],
|
|
3201
|
+
"summary": { "pass": 2, "fail": 1, "unavailable": 1 },
|
|
3202
|
+
"mode": "serve"
|
|
3203
|
+
}
|
|
3204
|
+
```
|
|
3205
|
+
|
|
3206
|
+
`voltro doctor`'s rule set is deliberately NOT here: it is a source-tree scan with its own allowlist file and exit-code contract, it would answer `unavailable` on the one deployment shape this surface exists to reach, and re-hosting it behind HTTP would be a second implementation of a large thing. Run the command, on the machine that has the source.
|
|
3207
|
+
|
|
2678
3208
|
## Streamable HTTP transport
|
|
2679
3209
|
|
|
2680
3210
|
For clients that speak MCP over HTTP, **`voltro-mcp-http`** serves the same surface over the current **Streamable HTTP** transport (the single-endpoint POST/GET model that replaced the old HTTP+SSE dual-endpoint). One endpoint handles:
|
|
@@ -2698,7 +3228,7 @@ The manifest is read through a TTL-cached source (~10 seconds): a procedure you
|
|
|
2698
3228
|
|
|
2699
3229
|
## Protocol scope
|
|
2700
3230
|
|
|
2701
|
-
MCP over JSON-RPC 2.0. `initialize` negotiates the protocol revision (`2025-06-18`, `2025-03-26`, `2024-11-05`) and advertises the `tools`, `resources`, and `prompts` capabilities; methods are `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`, `prompts/get`. The stdio bin frames this as newline-delimited JSON-RPC; the HTTP bin serves it over Streamable HTTP. Both transports route to the same
|
|
3231
|
+
MCP over JSON-RPC 2.0. `initialize` negotiates the protocol revision (`2025-06-18`, `2025-03-26`, `2024-11-05`) and advertises the `tools`, `resources`, and `prompts` capabilities; methods are `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, `prompts/list`, `prompts/get`. The stdio bin frames this as newline-delimited JSON-RPC; the HTTP bin serves it over Streamable HTTP. Both transports route to the same protocol core, all exported from `@voltro/mcp`: the pure `handleMcpRequest` (`callTool`, `listResources`/`readResource`, `listPrompts`/`getPrompt`) plus `handleMcpRequestAsync`, which handles the two methods that need a round trip to the app — `tools/list` folds in the admitted agent tools, and `tools/call` executes one or runs the invariant checks. With no live connection configured, `handleMcpRequestAsync` behaves exactly like the pure one.
|
|
2702
3232
|
|
|
2703
3233
|
|
|
2704
3234
|
|
|
@@ -2707,13 +3237,24 @@ MCP over JSON-RPC 2.0. `initialize` negotiates the protocol revision (`2025-06-1
|
|
|
2707
3237
|
<!-- source: en/cli/update.md -->
|
|
2708
3238
|
## Update
|
|
2709
3239
|
|
|
2710
|
-
_voltro update —
|
|
3240
|
+
_voltro update — what the command does and does not do. It bumps and installs; it does not make your app boot. The boot refusals this release ships, and the order you meet them._
|
|
2711
3241
|
|
|
2712
3242
|
`voltro update` upgrades an app to the latest framework release. It does three things in order:
|
|
2713
3243
|
|
|
2714
3244
|
1. **Bump** every `@voltro/*` dependency in `package.json` to the target version.
|
|
2715
3245
|
2. **Install** with your project's package manager — see [Which package manager](#which-package-manager) below.
|
|
2716
|
-
3. **Run the codemods** shipped with the target version
|
|
3246
|
+
3. **Run the codemods** shipped with the target version. A codemod is either a **transform** (rewrites your source) or **`manual`** (prints written steps, only when your app is affected). **Every one of 0.34.0's 21 codemods is `manual`** — nothing is rewritten for you, and there is no diff to review afterwards.
|
|
3247
|
+
|
|
3248
|
+
**`voltro update` does not make your app boot.** It moves versions and prints
|
|
3249
|
+
instructions; deciding what those instructions mean for your code is yours.
|
|
3250
|
+
0.34.0 ships **six boot refusals** — three of them fire on `voltro dev`, before
|
|
3251
|
+
you deploy anything — and `update`, `db apply` and `typecheck` all pass while an
|
|
3252
|
+
app is dead in every one of them. Start with [`voltro doctor`](#start-here-voltro-doctor),
|
|
3253
|
+
then read [the boot refusals](#the-boot-refusals-and-where-you-meet-them).
|
|
3254
|
+
|
|
3255
|
+
For the per-change narrative — what each of the 21 notes is about and why —
|
|
3256
|
+
read [Upgrading to 0.34.0](/docs/releases/upgrading-to-0-34). This page is the
|
|
3257
|
+
command's own contract.
|
|
2717
3258
|
|
|
2718
3259
|
```bash
|
|
2719
3260
|
voltro update # bump to the latest published version, install, run codemods
|
|
@@ -2729,7 +3270,219 @@ voltro update --codemods-only --from 0.3.0 # re-run codemods 0.3.0
|
|
|
2729
3270
|
voltro update --codemods-only --from 0.3.0 --to 0.4.0 # explicit delta
|
|
2730
3271
|
```
|
|
2731
3272
|
|
|
2732
|
-
|
|
3273
|
+
## Start here: `voltro doctor`
|
|
3274
|
+
|
|
3275
|
+
`voltro doctor` is the command that lists what will refuse to boot, and it
|
|
3276
|
+
reports the **exact set the boot refuses on, from the same function** — not a
|
|
3277
|
+
second implementation that can disagree with it.
|
|
3278
|
+
|
|
3279
|
+
```bash
|
|
3280
|
+
voltro update
|
|
3281
|
+
voltro doctor # every undecided procedure + every unverified webhook, by tag and file
|
|
3282
|
+
voltro doctor --json # accessDecisions.undecided / webhookVerification.unverified — for CI
|
|
3283
|
+
```
|
|
3284
|
+
|
|
3285
|
+
Run it before you try to start anything. Its two most important sections are the
|
|
3286
|
+
two source-shaped refusals below:
|
|
3287
|
+
|
|
3288
|
+
```text
|
|
3289
|
+
access decisions · security.defaultDeny ON
|
|
3290
|
+
✗ no access decision 18
|
|
3291
|
+
✓ openAccess, declared on purpose 0
|
|
3292
|
+
|
|
3293
|
+
✗ notes.list (query)
|
|
3294
|
+
api/notes/list.query.ts
|
|
3295
|
+
…
|
|
3296
|
+
|
|
3297
|
+
`voltro dev` and `voltro serve` REFUSE to boot on these. Give each a decision:
|
|
3298
|
+
`guards: [{ scope: '…' }]`, or `openAccess: '<why anyone may call it>'`.
|
|
3299
|
+
```
|
|
3300
|
+
|
|
3301
|
+
**What doctor cannot see.** It reads your source tree, so it covers the access
|
|
3302
|
+
decisions and the webhook declarations. The other four refusals are properties
|
|
3303
|
+
of your *environment* — a connection URL, `NODE_ENV`, a migration that has not
|
|
3304
|
+
run against a particular database — and no source scan can predict them. Read
|
|
3305
|
+
the list below for those.
|
|
3306
|
+
|
|
3307
|
+
## The boot refusals, and where you meet them
|
|
3308
|
+
|
|
3309
|
+
Six of them ship in 0.34.0. `voltro update` succeeds, `voltro db apply`
|
|
3310
|
+
succeeds and `voltro typecheck` succeeds in **all six** — the access decision is
|
|
3311
|
+
a runtime boot gate, not a type error, and the other five are environment facts
|
|
3312
|
+
no compiler is looking at. They are listed here in the order you actually meet
|
|
3313
|
+
them: the first three on your own machine, the last three in a container.
|
|
3314
|
+
|
|
3315
|
+
### On your laptop — `voltro dev`
|
|
3316
|
+
|
|
3317
|
+
**1. A wire-exposed procedure that decides nothing.** `guards:` used to default
|
|
3318
|
+
to *allowed*, so a `*.query.ts` with no guard was callable by any authenticated
|
|
3319
|
+
session. Every discovered procedure now declares `guards:` or `openAccess:`, or
|
|
3320
|
+
neither `voltro dev` nor `voltro serve` starts.
|
|
3321
|
+
|
|
3322
|
+
```text
|
|
3323
|
+
[access] 18 wire-exposed procedures declare no access decision, and this app runs with `security.defaultDeny`:
|
|
3324
|
+
|
|
3325
|
+
notes.list (query)
|
|
3326
|
+
api/notes/list.query.ts
|
|
3327
|
+
…
|
|
3328
|
+
|
|
3329
|
+
Each of these is callable by ANY authenticated session. Give each one a
|
|
3330
|
+
decision — the two are equally acceptable and they are not the same claim:
|
|
3331
|
+
|
|
3332
|
+
guards: [{ scope: 'invoices:read' }] the caller must hold a scope
|
|
3333
|
+
openAccess: 'public pricing, no user data' anyone may call it, and why
|
|
3334
|
+
```
|
|
3335
|
+
|
|
3336
|
+
Three things worth knowing before you start editing:
|
|
3337
|
+
|
|
3338
|
+
- **`openAccess` takes a reason, not a boolean.** It is what makes "we decided
|
|
3339
|
+
this is open" distinguishable from "nobody looked".
|
|
3340
|
+
- **Do not rubber-stamp with a scope every caller already holds.** That
|
|
3341
|
+
satisfies the gate, reads as protection, and enforces nothing.
|
|
3342
|
+
- **A procedure only other server code calls wants neither.** Mark it
|
|
3343
|
+
`internal: true` and it leaves the wire entirely (and then it must not carry
|
|
3344
|
+
`openAccess` — the definers refuse that combination).
|
|
3345
|
+
- **Your plugins' procedures are not your problem.** The gate reads your app's
|
|
3346
|
+
own discovered files only.
|
|
3347
|
+
|
|
3348
|
+
**The one-field escape hatch**, if you need to ship before you have decided
|
|
3349
|
+
everything:
|
|
3350
|
+
|
|
3351
|
+
```ts
|
|
3352
|
+
// app.config.ts
|
|
3353
|
+
export default { security: { defaultDeny: false } }
|
|
3354
|
+
```
|
|
3355
|
+
|
|
3356
|
+
That restores the old default-allow for the WHOLE app, in one place a reviewer
|
|
3357
|
+
can see. There is deliberately no env var for it — an env var is how a security
|
|
3358
|
+
default gets turned off in one CI job and stays off. `voltro doctor` keeps
|
|
3359
|
+
listing the undecided procedures while it is off, marked advisory.
|
|
3360
|
+
|
|
3361
|
+
**2. An incoming webhook that does not say how it authenticates its caller.**
|
|
3362
|
+
An incoming webhook is a public, unauthenticated POST that runs your
|
|
3363
|
+
application code. The transport refuses to mount one that declared nothing:
|
|
3364
|
+
|
|
3365
|
+
```text
|
|
3366
|
+
incoming webhook '/webhooks/stripe' is mounted without declaring how it authenticates its caller.
|
|
3367
|
+
An incoming webhook is a public POST that runs your application code, so the framework refuses
|
|
3368
|
+
to mount one that nothing verifies. Declare it on the descriptor:
|
|
3369
|
+
· provider: stripeWebhookProvider() — or any provider preset (HMAC + replay window)
|
|
3370
|
+
· signature: { _tag: 'hmac', algorithm: 'hmacSha256', header: 'X-Signature', ... }
|
|
3371
|
+
· verification: 'provider' — the handler verifies with the provider's own SDK
|
|
3372
|
+
· verification: 'none' — deliberately public (gateway / IP allow-list owns it)
|
|
3373
|
+
A signature-verified webhook also needs its shared secret in VOLTRO_WEBHOOK_SECRET_<ID>;
|
|
3374
|
+
the framework mints no secret for you.
|
|
3375
|
+
```
|
|
3376
|
+
|
|
3377
|
+
`verification: 'none'` is a legitimate answer when a gateway or IP allow-list
|
|
3378
|
+
owns the trust boundary. It has to be *said*, which is the whole change.
|
|
3379
|
+
|
|
3380
|
+
**3. A mysql / mariadb / mssql URL asking for TLS the dialect cannot honour.**
|
|
3381
|
+
Both dialects used to DROP a TLS request rather than reject it, so
|
|
3382
|
+
`DB_URL=mysql://…?ssl=true` connected in plaintext with no warning. Only the two
|
|
3383
|
+
modes the cross-dialect `ssl` boolean can express are accepted; everything else
|
|
3384
|
+
throws where the connection is built — which is `voltro dev`, `voltro serve`,
|
|
3385
|
+
`voltro db apply` and `voltro migrate` alike.
|
|
3386
|
+
|
|
3387
|
+
```text
|
|
3388
|
+
DB_URL '?sslmode=verify-full' is not supported by the mysql/mariadb dialect —
|
|
3389
|
+
use 'require' (TLS without certificate verification) or 'disable' (plaintext).
|
|
3390
|
+
```
|
|
3391
|
+
|
|
3392
|
+
| URL says | Result |
|
|
3393
|
+
|---|---|
|
|
3394
|
+
| `?sslmode=require` / `?ssl=true` / `?ssl=1` | TLS, certificate NOT verified |
|
|
3395
|
+
| `?encrypt=1` | same (mssql only — tedious' spelling) |
|
|
3396
|
+
| `?sslmode=disable` / `?ssl=false` / `?ssl=0` | plaintext, explicitly |
|
|
3397
|
+
| `prefer`, `allow`, `verify-ca`, `verify-full`, `?ssl=yes`, a CA-profile name | **throws at boot** |
|
|
3398
|
+
|
|
3399
|
+
If your URL said `?ssl=true` you were being lied to — that connection has been
|
|
3400
|
+
plaintext, and it is real now. **Confirm your server accepts TLS before rolling
|
|
3401
|
+
out.** Check it from the database rather than from the config:
|
|
3402
|
+
|
|
3403
|
+
```sql
|
|
3404
|
+
-- mysql / mariadb: empty = plaintext, a cipher name = encrypted
|
|
3405
|
+
SHOW STATUS LIKE 'Ssl_cipher';
|
|
3406
|
+
-- mssql: FALSE / TRUE
|
|
3407
|
+
SELECT encrypt_option FROM sys.dm_exec_connections WHERE session_id = @@SPID;
|
|
3408
|
+
```
|
|
3409
|
+
|
|
3410
|
+
postgres, sqlite and turso are unaffected.
|
|
3411
|
+
|
|
3412
|
+
### In the container — `voltro serve` / `voltro start`
|
|
3413
|
+
|
|
3414
|
+
These three fire only on a deploy environment (`NODE_ENV=production` or
|
|
3415
|
+
`staging`), which is exactly why they are the expensive ones: nothing on your
|
|
3416
|
+
machine reproduces them.
|
|
3417
|
+
|
|
3418
|
+
**4. Pending `migrations/*.migration.ts` that have never run against this
|
|
3419
|
+
database.** It always had to run before serve; what changed is that skipping it
|
|
3420
|
+
is loud. Serve's other schema guard is a declarative fingerprint diff, and a
|
|
3421
|
+
file migration exists for the changes a state diff cannot infer — a data move, a
|
|
3422
|
+
backfill, a cross-table rewrite. Those move no fingerprint, so the guard passed
|
|
3423
|
+
and production ran un-migrated.
|
|
3424
|
+
|
|
3425
|
+
```text
|
|
3426
|
+
serve: refusing to boot — 3 pending file-based migration(s) have never run against this
|
|
3427
|
+
database. They perform the changes a schema diff cannot infer (data moves, backfills, table
|
|
3428
|
+
splits), so the declarative fingerprint check below cannot see them and would have let this
|
|
3429
|
+
process serve un-migrated data.
|
|
3430
|
+
|
|
3431
|
+
Run them from your pre-deploy job — `voltro db migrate .` (schema + files) or `voltro db files .`
|
|
3432
|
+
(files alone) — or set VOLTRO_AUTO_MIGRATE=0 to bypass every boot schema check. `voltro serve`
|
|
3433
|
+
never applies them itself: a rolling deploy would start N replicas and each would try.
|
|
3434
|
+
```
|
|
3435
|
+
|
|
3436
|
+
Serve will not apply them for you, deliberately: a rolling deploy starts N
|
|
3437
|
+
replicas, each would try, and the migration lock turns that into N-1 processes
|
|
3438
|
+
blocked on boot. A refusal is recoverable in one command; a fleet wedged behind
|
|
3439
|
+
a lock is not.
|
|
3440
|
+
|
|
3441
|
+
**5. `plugin-search` on the in-memory backend.** The heap-resident index is
|
|
3442
|
+
per-process AND non-durable — it starts empty after every deploy and nothing
|
|
3443
|
+
re-seeds it — so a single replica does not make it correct.
|
|
3444
|
+
|
|
3445
|
+
```text
|
|
3446
|
+
plugin-search refuses to boot in production on the in-memory backend.
|
|
3447
|
+
|
|
3448
|
+
The memory index lives in THIS process's heap. Two consequences, both silent:
|
|
3449
|
+
• every replica holds a different index, so a result depends on which replica served you;
|
|
3450
|
+
• the index starts EMPTY after every restart/deploy, and nothing re-seeds it automatically.
|
|
3451
|
+
|
|
3452
|
+
Configure a durable engine in app.config.ts:
|
|
3453
|
+
searchPlugin({ backend: { engine: 'typesense', url: …, apiKey: … }, indexes })
|
|
3454
|
+
…
|
|
3455
|
+
```
|
|
3456
|
+
|
|
3457
|
+
If your deployment genuinely is one process that calls `backfillIndex` at
|
|
3458
|
+
startup, declare it: `searchPlugin({ singleProcessMemoryIndex: true, indexes })`
|
|
3459
|
+
— a claim the plugin holds you to, not a mute switch. Full reasoning:
|
|
3460
|
+
[the memory backend refuses to boot in production](/docs/plugins/search#the-memory-backend-refuses-to-boot-in-production).
|
|
3461
|
+
|
|
3462
|
+
**6. `SSR_CACHE=postgres` with no database in the web process's environment.**
|
|
3463
|
+
`voltro start` used to select the postgres ISR cache only when `PG_HOST` was
|
|
3464
|
+
set, while every template and every deployment doc configures `DB_URL` — so an
|
|
3465
|
+
app that asked for the shared cache the documented way silently got the
|
|
3466
|
+
per-process memory one, reported at `info` as if it were the default. Both sides
|
|
3467
|
+
go through the connection resolver now, and the mismatch is fatal on a deploy
|
|
3468
|
+
environment:
|
|
3469
|
+
|
|
3470
|
+
```text
|
|
3471
|
+
SSR_CACHE=postgres, but nothing in the environment names a database (looked for DB_URL,
|
|
3472
|
+
DB_PRIMARY_URL, DB_DIRECT_URL, DB_MIGRATE_URL, DB_HOST, PG_HOST). Refusing to fall back to
|
|
3473
|
+
the per-process memory cache: it is not shared between instances and does not survive a
|
|
3474
|
+
restart, so the pages this process serves would differ from its replicas' with nothing to
|
|
3475
|
+
indicate it.
|
|
3476
|
+
```
|
|
3477
|
+
|
|
3478
|
+
Either give the web process a `DB_URL`, or drop `SSR_CACHE=postgres` and take
|
|
3479
|
+
the memory cache deliberately. Off a deploy environment it warns and falls back
|
|
3480
|
+
instead. Two knock-on effects with nothing to edit: pages declaring
|
|
3481
|
+
`cacheInvalidatesOn` that had *no* live invalidation now have it (a real change
|
|
3482
|
+
in origin load), and a web process with no database that declares
|
|
3483
|
+
`cacheInvalidatesOn` gets a boot warning naming those routes.
|
|
3484
|
+
|
|
3485
|
+
## Taking only part of the jump — `--only`
|
|
2733
3486
|
|
|
2734
3487
|
Ids are what `--dry-run` prints:
|
|
2735
3488
|
|
|
@@ -2803,7 +3556,16 @@ source version.
|
|
|
2803
3556
|
|
|
2804
3557
|
## The clean-tree guard
|
|
2805
3558
|
|
|
2806
|
-
|
|
3559
|
+
`voltro update` refuses to run on a dirty git working tree — commit or stash
|
|
3560
|
+
first. Use `--dry-run` to preview without touching anything, or `--force` to
|
|
3561
|
+
override the guard (you accept a mixed diff).
|
|
3562
|
+
|
|
3563
|
+
The guard is about the writes `update` makes on your behalf: the version bump
|
|
3564
|
+
across every workspace `package.json`, the lockfile the install rewrites, and —
|
|
3565
|
+
in a release that ships one — a **transform** codemod rewriting your source.
|
|
3566
|
+
When the jump's codemods are all `manual`, as 0.34.0's 21 are, `update` writes
|
|
3567
|
+
nothing under `src/` at all, and the work the printed notes describe is a
|
|
3568
|
+
separate commit you author yourself.
|
|
2807
3569
|
|
|
2808
3570
|
`--help` / `-h` is answered *before* the guard, so `voltro update --help` prints the flag list even on a dirty tree. The same holds for `voltro doctor --help`.
|
|
2809
3571
|
|
|
@@ -2882,27 +3644,68 @@ The same resolved manager is used for the **registry lookup** of the latest vers
|
|
|
2882
3644
|
|
|
2883
3645
|
## Codemods
|
|
2884
3646
|
|
|
2885
|
-
Each breaking public-API change in a release ships a **codemod
|
|
3647
|
+
Each breaking public-API change in a release ships a **codemod**, and there are exactly two kinds:
|
|
3648
|
+
|
|
3649
|
+
- A **transform codemod** rewrites your source automatically — renamed imports, moved modules, changed component props, restructured call signatures. The rewrite is scoped to files that actually import the affected symbol. Where the affected sites can be found but the fix needs your judgment, it inserts `// TODO(voltro-migration): …` markers so you can locate every spot.
|
|
3650
|
+
- A **manual codemod** prints written steps during the update, `appliesTo`-gated so you see it only when your app is actually affected. It writes nothing.
|
|
3651
|
+
|
|
3652
|
+
**0.34.0's are all manual — 21 of them, zero transforms.** That is not an
|
|
3653
|
+
omission. The largest change in the release asks a question only you can answer
|
|
3654
|
+
("who may call this procedure?"), and a transform could have answered it
|
|
3655
|
+
mechanically for every procedure in your app — declaring your entire surface
|
|
3656
|
+
open on purpose, in one commit nobody reads, with a reason the tool invented.
|
|
3657
|
+
The framework does not sign that.
|
|
3658
|
+
|
|
3659
|
+
So on this jump the output is a wall of text and no diff:
|
|
3660
|
+
|
|
3661
|
+
```text
|
|
3662
|
+
Manual steps required (could not be automated):
|
|
3663
|
+
|
|
3664
|
+
▸ 0.34.0/03_procedure-access-decision — Every wire-exposed procedure declares an access decision (`guards:` or `openAccess:`)
|
|
3665
|
+
YOUR APP WILL NOT BOOT UNTIL EVERY WIRE-EXPOSED PROCEDURE DECIDES WHO MAY
|
|
3666
|
+
CALL IT. …
|
|
3667
|
+
```
|
|
2886
3668
|
|
|
2887
|
-
|
|
2888
|
-
|
|
3669
|
+
Read the notes. They are the only artefact the upgrade produces, and each one
|
|
3670
|
+
prints only because your tree matched it.
|
|
2889
3671
|
|
|
2890
|
-
Codemods that span multiple versions run in order (e.g. upgrading `0.2.0 → 0.4.0` runs the `0.3.0` and `0.4.0` codemods in sequence).
|
|
3672
|
+
Codemods that span multiple versions run in order (e.g. upgrading `0.2.0 → 0.4.0` runs the `0.3.0` and `0.4.0` codemods in sequence).
|
|
2891
3673
|
|
|
2892
3674
|
## The database is separate
|
|
2893
3675
|
|
|
2894
|
-
`voltro update` does **not** touch your database. Framework-owned `_voltro_*` tables (workflow runs, schedules, …) are reconciled by the declarative differ, not by codemods: when a release changes one of those tables, your next `voltro db apply` (or `voltro dev` boot, which auto-applies) picks up the change.
|
|
3676
|
+
`voltro update` does **not** touch your database. Framework-owned `_voltro_*` tables (workflow runs, schedules, …) are reconciled by the declarative differ, not by codemods: when a release changes one of those tables, your next `voltro db apply` (or `voltro dev` boot, which auto-applies) picks up the change.
|
|
3677
|
+
|
|
3678
|
+
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
|
+
|
|
3680
|
+
## After the update — the checklist
|
|
2895
3681
|
|
|
2896
3682
|
```bash
|
|
2897
3683
|
voltro update
|
|
2898
|
-
voltro
|
|
2899
|
-
|
|
3684
|
+
voltro doctor # ← the one that can fail. Every undecided procedure + unverified webhook.
|
|
3685
|
+
voltro db apply # reconcile any changed framework tables
|
|
3686
|
+
voltro typecheck # your code against the new API surface
|
|
3687
|
+
voltro dev # the first boot that actually exercises the gates
|
|
2900
3688
|
```
|
|
2901
3689
|
|
|
2902
|
-
|
|
3690
|
+
**`update`, `db apply` and `typecheck` all pass on an app that will not start.**
|
|
3691
|
+
That is the shape to internalise: the access decision is a runtime boot gate, not
|
|
3692
|
+
a type error; the webhook declaration is a descriptor property, not a signature;
|
|
3693
|
+
and the environment-shaped refusals are facts about a container you have not
|
|
3694
|
+
started yet. The only two steps in that list that can tell you the truth are
|
|
3695
|
+
`voltro doctor` and an actual boot.
|
|
3696
|
+
|
|
3697
|
+
For a deploy, add the container-side ones to your pre-deploy job before the
|
|
3698
|
+
image rolls:
|
|
3699
|
+
|
|
3700
|
+
```bash
|
|
3701
|
+
voltro db migrate . # schema diff AND file migrations — refusal 4
|
|
3702
|
+
# and check by hand: the mysql/mssql DB_URL's ?sslmode (3), a durable search
|
|
3703
|
+
# backend (5), and DB_URL on the WEB process if it sets SSR_CACHE=postgres (6)
|
|
3704
|
+
```
|
|
2903
3705
|
|
|
2904
3706
|
## Where to read next
|
|
2905
3707
|
|
|
3708
|
+
- [Upgrading to 0.34.0](/docs/releases/upgrading-to-0-34) — the per-change narrative behind the 21 notes
|
|
2906
3709
|
- [Migrate](/docs/cli/migrate) — schema changes end-to-end
|
|
2907
3710
|
- [Build & start](/docs/cli/build-and-start) — production paths
|
|
2908
3711
|
|