@voltro/cli 0.33.0 → 0.34.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 +1801 -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-BICVGnEC.js +2 -0
- package/dist/{apiBuild-h9VHtnlw.js → apiBuild-DZF_f0_I.js} +46 -46
- 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-CE7Ca9gm.js +711 -0
- package/dist/cacheCommand-DA4OH9xt.js +42 -0
- package/dist/capabilitiesCommand-nq_pz5xd.js +123 -0
- package/dist/checkCommand-Dv8r9tGO.js +231 -0
- package/dist/checkCommand-YZDmkAGd.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-CNXKlcvD.js +54 -0
- package/dist/codegen-CYM3Zqrf.js +605 -0
- package/dist/codegen-ChBi_hVa.js +2 -0
- package/dist/codegenCommand-DC6w2tNZ.js +30 -0
- package/dist/codemodRunner-DRRqXR74.js +5243 -0
- package/dist/commandRunner-BLAEFLjp.js +47 -0
- package/dist/commands-gutsz-Ac.js +808 -0
- package/dist/connectionConfig-UFlIEiys.js +66 -0
- package/dist/dashboardCommand-3YG8p-UA.js +25 -0
- package/dist/dataCommand-qL0r7fPO.js +535 -0
- package/dist/dataProfile-dW-PsfLB.js +15 -0
- package/dist/{dbCommand-DTLKAfbA.js → dbCommand-B6X0FZbq.js} +551 -401
- package/dist/dbCommand-CpdKLeQq.js +2 -0
- package/dist/{dev-C_P8FLSx.js → dev-CerMd0mW.js} +3122 -3129
- package/dist/dev-CoG-ZPx8.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-BvqGBwNG.js +2 -0
- package/dist/{checkCommand-xGhRFFg2.js → doctorCommand-CnDXQxfa.js} +520 -1278
- package/dist/dormancyCommand-Dszo57d6.js +69 -0
- package/dist/e2eCmd-BRabZww-.js +147 -0
- package/dist/embeddingsCommand-C0sKVRo1.js +73 -0
- package/dist/envCommand-DPgdV1Bq.js +60 -0
- package/dist/evalCommand-6RUfPen4.js +118 -0
- package/dist/evolveCommand-DHpkgjgH.js +281 -0
- package/dist/fileTaxonomy-CJfgOllU.js +457 -0
- package/dist/frameworkTableAssembly-BGHmck-x.js +2 -0
- package/dist/{frameworkTableAssembly-BwIrO5nv.js → frameworkTableAssembly-DkkP6BgC.js} +184 -148
- package/dist/generateCommand-oibemh97.js +147 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +4 -3
- package/dist/infoCommand-BJw9nLUR.js +60 -0
- package/dist/{inspect-BUUjt773.js → inspect-CBqFtAKA.js} +82 -40
- package/dist/inspect-C_T_WGvl.js +2 -0
- package/dist/inspectCmd-Bppy-GGw.js +224 -0
- package/dist/inspectFetch-Cm8_wVvp.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-CCca3KRZ.js +260 -0
- package/dist/manifestBuild-ChsKAhmn.js +2 -0
- package/dist/{manifestBuild-BLrVuSlM.js → manifestBuild-sxpwdKY1.js} +1 -1
- package/dist/metaCommands-7MJfZ5cf.js +196 -0
- package/dist/migrate-CBwOt_iV.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/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-CRxalXTs.js +91 -0
- package/dist/scheduleCmd--jksTrf6.js +69 -0
- package/dist/scheduleManifestCmd-D2x0CTTY.js +249 -0
- package/dist/schemaIr-UJybUUZW.js +103 -0
- package/dist/{sdkgen-C81QIkiL.js → sdkgen-CYJscZC7.js} +111 -209
- package/dist/seedRunner-TFHHiToI.js +329 -0
- package/dist/serveCommand-B_isw7q4.js +1647 -0
- package/dist/serveCommand-DOvbgRnQ.js +2 -0
- package/dist/serveEntry.js +5 -5
- package/dist/serverlessCommand-CfJZy6dS.js +482 -0
- package/dist/start-9LiUOfES.js +1087 -0
- package/dist/start-B-9Nsp-S.js +3 -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-CkEZQrtt.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-Bkptutss.js +585 -0
- package/dist/updateCommand-us1_hdIC.js +2 -0
- package/dist/{inspectMetrics-1xzTKAFx.js → webDev-CBYvPqQr.js} +975 -1559
- package/dist/webDev-Cg-fFiyd2.js +2 -0
- package/dist/webhookDiscovery-CrGAfhIG.js +2 -0
- package/dist/webhookDiscovery-D7VaeMlz.js +51 -0
- package/dist/webhooksCommand-CID96Rga.js +267 -0
- package/dist/workflowsCmd-D1VTmLMY.js +608 -0
- package/package.json +179 -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 +22 -5
- package/templates/agent-docs/ai.md +370 -0
- package/templates/agent-docs/authentication.md +265 -31
- package/templates/agent-docs/caching.md +6 -0
- package/templates/agent-docs/cli.md +794 -50
- package/templates/agent-docs/data.md +550 -11
- package/templates/agent-docs/database/migrations.md +174 -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 +69 -3
- package/templates/agent-docs/introduction.md +87 -16
- package/templates/agent-docs/local-first-mobile.md +79 -4
- package/templates/agent-docs/multi-tenancy.md +95 -20
- package/templates/agent-docs/observability.md +58 -3
- package/templates/agent-docs/plugins/ai-flows.md +161 -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 +1 -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 +157 -6
- package/templates/agent-docs/plugins/sso-saml.md +47 -8
- package/templates/agent-docs/plugins/webhooks.md +105 -0
- package/templates/agent-docs/plugins.md +150 -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 +10 -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 +1700 -104
- package/templates/agent-docs/workflows.md +130 -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/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-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
|
@@ -46,9 +46,11 @@ The implementation is the [Vercel AI SDK](https://sdk.vercel.ai) wrapped behind
|
|
|
46
46
|
- [Providers](/docs/ai/providers) — Anthropic, OpenAI, the Vercel AI Gateway, mock; per-config keys (BYOK), switching at runtime, model-wrapping middleware
|
|
47
47
|
- [Agents](/docs/ai/agents) — `*.agent.tsx` shape, system prompts, tool wiring
|
|
48
48
|
- [Tools](/docs/ai/tools) — `*.tool.tsx` shape, validation, side effects
|
|
49
|
+
- [MCP clients](/docs/ai/mcp-clients) — mount an EXTERNAL MCP server's tools, through the same allow/deny policy, with the untrusted server bounded
|
|
49
50
|
- [Streaming](/docs/ai/streaming) — tokens-over-WebSocket, client hooks, backpressure
|
|
50
51
|
- [RAG](/docs/ai/rag) — pgvector, embedding mixin, hybrid search
|
|
51
52
|
- [Cost tracking](/docs/ai/cost-tracking) — the token `usage` returned on every call
|
|
53
|
+
- [Prompt versioning](/docs/ai/prompt-versioning) — `definePrompt`, a content digest as the version, and which prompt version produced (and cost) a given run
|
|
52
54
|
|
|
53
55
|
## Why one surface
|
|
54
56
|
|
|
@@ -982,10 +984,12 @@ authorization path), by construction.
|
|
|
982
984
|
// Opt a descriptor in (a description is REQUIRED — the model needs it):
|
|
983
985
|
export const listOrders = defineQuery({
|
|
984
986
|
name: 'orders.list', input: ListInput, output: Schema.Array(Order), source: 'orders',
|
|
987
|
+
guards: [{ scope: 'orders:read' }], // the tool inherits exactly this check
|
|
985
988
|
exposeAsTool: { description: "List the current tenant's orders." },
|
|
986
989
|
})
|
|
987
990
|
export const createOrder = defineMutation({
|
|
988
991
|
name: 'orders.create', input: CreateInput, output: Order, target: { table: 'orders', op: 'insert' },
|
|
992
|
+
guards: [{ scope: 'orders:write' }],
|
|
989
993
|
exposeAsTool: { description: 'Create an order.', confirm: true }, // writes confirm by default
|
|
990
994
|
})
|
|
991
995
|
```
|
|
@@ -999,6 +1003,14 @@ const tools = appTools(entries, { allow: ['orders.*'], includeWrites: true })
|
|
|
999
1003
|
const { object } = yield* generateObjectWithTools({ prompt, tools, schema: Result })
|
|
1000
1004
|
```
|
|
1001
1005
|
|
|
1006
|
+
**The guards are load-bearing, not incidental.** "The agent can do nothing the
|
|
1007
|
+
subject couldn't" is a claim about the descriptor's own access decision — the
|
|
1008
|
+
synthesized tool runs the real handler under the calling subject and re-checks
|
|
1009
|
+
the same `guards:`. Exposing a procedure whose decision is `openAccess:` gives
|
|
1010
|
+
the model an unchecked endpoint, which is the right call for a public price
|
|
1011
|
+
lookup and the wrong one for anything reading rows. (A descriptor with *no*
|
|
1012
|
+
decision cannot reach this page: the app would not have booted.)
|
|
1013
|
+
|
|
1002
1014
|
Safety defaults (don't override blindly): **reads are included, writes are
|
|
1003
1015
|
opt-in** (`includeWrites: true`) and **confirm by default**. Put destructive
|
|
1004
1016
|
tags on `deny`. `exposeAsTool: true` alone does NOT expose — a tool with no
|
|
@@ -1006,6 +1018,60 @@ description is unusable; always use the object form. The annotations also
|
|
|
1006
1018
|
surface in the capability manifest, so a coding agent discovers what's
|
|
1007
1019
|
tool-exposable.
|
|
1008
1020
|
|
|
1021
|
+
### `confirm` is enforced, not reported
|
|
1022
|
+
|
|
1023
|
+
A `confirm` tool is admitted only when its descriptor also declares
|
|
1024
|
+
[`requiresApproval`](/docs/data/approvals). Otherwise it is REFUSED — with the
|
|
1025
|
+
fix in the reason — because the alternative is to mount it and hope the caller
|
|
1026
|
+
asks, and the caller is a model.
|
|
1027
|
+
|
|
1028
|
+
```ts
|
|
1029
|
+
export const refundOrder = defineMutation({
|
|
1030
|
+
name: 'orders.refund',
|
|
1031
|
+
// …
|
|
1032
|
+
guards: [{ scope: 'orders:refund' }],
|
|
1033
|
+
exposeAsTool: { description: 'Refund an order.' }, // confirm: true by default
|
|
1034
|
+
requiresApproval: { approvers: [{ scope: 'orders:approve' }] },
|
|
1035
|
+
})
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
With that pair, the agent's call parks in your app's own approval queue and comes
|
|
1039
|
+
back as a typed `ApprovalRequired` carrying an id; a human decides in your UI;
|
|
1040
|
+
the identical call then succeeds exactly once. The human step is enforced by the
|
|
1041
|
+
handler, so nothing depends on the agent harness honouring a flag.
|
|
1042
|
+
|
|
1043
|
+
The inventory reports both `confirm` and `approvalBacked`, because "why is this
|
|
1044
|
+
tool not executable" is answered only by the pair. For an **external** MCP
|
|
1045
|
+
server's tools `approvalBacked` is always false: their handler is behind an HTTP
|
|
1046
|
+
boundary we do not own, so there is no point at which we could hold the call —
|
|
1047
|
+
the human decision for an external write is the declaration-time one (`allow` is
|
|
1048
|
+
required, `includeWrites` is opt-in).
|
|
1049
|
+
|
|
1050
|
+
### The same toolset, to an EXTERNAL agent
|
|
1051
|
+
|
|
1052
|
+
`appTools` is the in-process form: your own agent loop, in your own handler. The
|
|
1053
|
+
same descriptors — through the same admission decision (`appToolDecision`, which
|
|
1054
|
+
`appTools` itself filters on) — can also be handed to an external MCP peer, so
|
|
1055
|
+
Claude Code or Cursor calls your procedures directly:
|
|
1056
|
+
|
|
1057
|
+
```ts
|
|
1058
|
+
// app.config.ts
|
|
1059
|
+
export default {
|
|
1060
|
+
agents: {
|
|
1061
|
+
tools: { allow: ['orders.*'], includeWrites: true }, // the SAME AppToolPolicy
|
|
1062
|
+
mcp: true, // off by default
|
|
1063
|
+
},
|
|
1064
|
+
}
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
Five gates stand in front of it, the app credential the agent acts as is a
|
|
1068
|
+
separate header from the operator's inspect token, and an **unbacked** `confirm`
|
|
1069
|
+
tool is not mounted there at all (there is no human in that process). A
|
|
1070
|
+
`requiresApproval`-backed one IS mounted: the human is not in the transport, they
|
|
1071
|
+
are in your approval queue, so nothing there has to trust the client.
|
|
1072
|
+
The full list, and what it does not defend against, is on the
|
|
1073
|
+
[MCP server](/docs/cli/mcp) page.
|
|
1074
|
+
|
|
1009
1075
|
The end-user-facing counterpart is **`<AppAgent>`** (from `@voltro/web`) — a
|
|
1010
1076
|
"do it for me" chat whose ceiling is the logged-in subject's own permissions.
|
|
1011
1077
|
See [Schema-driven UI → Reactive components](/docs/ui/reactive-components).
|
|
@@ -1019,6 +1085,127 @@ See [Schema-driven UI → Reactive components](/docs/ui/reactive-components).
|
|
|
1019
1085
|
|
|
1020
1086
|
|
|
1021
1087
|
|
|
1088
|
+
---
|
|
1089
|
+
|
|
1090
|
+
<!-- source: en/ai/mcp-clients.md -->
|
|
1091
|
+
## MCP clients
|
|
1092
|
+
|
|
1093
|
+
_Mount an external MCP server's tools onto a Voltro agent — through the same allow/deny policy your own `exposeAsTool` descriptors go through, with the untrusted server bounded._
|
|
1094
|
+
|
|
1095
|
+
`@voltro/mcp` points **outward**: it exposes your app to a coding agent as an MCP server. `mcpToolset` points **inward** — it connects to somebody else's MCP server (GitHub, Slack, Sentry, a Postgres bridge) and mounts its tools onto a Voltro agent.
|
|
1096
|
+
|
|
1097
|
+
The two halves speak the same protocol; only the direction differs.
|
|
1098
|
+
|
|
1099
|
+
## Mounting a server
|
|
1100
|
+
|
|
1101
|
+
```ts
|
|
1102
|
+
import { generateObjectWithTools, httpMcpTransport, mcpToolset } from '@voltro/ai'
|
|
1103
|
+
|
|
1104
|
+
const github = yield* mcpToolset(
|
|
1105
|
+
{
|
|
1106
|
+
namespace: 'github',
|
|
1107
|
+
transport: httpMcpTransport({
|
|
1108
|
+
server: 'github',
|
|
1109
|
+
url: process.env.GITHUB_MCP_URL!,
|
|
1110
|
+
headers: { authorization: `Bearer ${process.env.GITHUB_MCP_TOKEN!}` },
|
|
1111
|
+
}),
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
allow: ['github.search_*', 'github.get_*'],
|
|
1115
|
+
readOnly: ['github.search_*', 'github.get_*'],
|
|
1116
|
+
},
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
const { object } = yield* generateObjectWithTools({
|
|
1120
|
+
prompt,
|
|
1121
|
+
tools: github.tools,
|
|
1122
|
+
schema: Result,
|
|
1123
|
+
})
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
`github.tools` is a `Record<string, AnyTool>` keyed by the namespaced tag (`github.get_issue`) — the same shape [`appTools`](/docs/ai/tools) produces, so one agent can mix its own tools and an external server's.
|
|
1127
|
+
|
|
1128
|
+
For a locally spawned server, use the stdio transport instead:
|
|
1129
|
+
|
|
1130
|
+
```ts
|
|
1131
|
+
import { stdioMcpTransport } from '@voltro/ai'
|
|
1132
|
+
|
|
1133
|
+
const files = yield* mcpToolset(
|
|
1134
|
+
{
|
|
1135
|
+
namespace: 'files',
|
|
1136
|
+
transport: stdioMcpTransport({
|
|
1137
|
+
server: 'files',
|
|
1138
|
+
command: 'npx',
|
|
1139
|
+
args: ['-y', 'some-mcp-server'],
|
|
1140
|
+
// The child does NOT inherit your process environment. Pass only what it needs.
|
|
1141
|
+
env: { HOME: process.env.HOME! },
|
|
1142
|
+
}),
|
|
1143
|
+
},
|
|
1144
|
+
{ allow: ['files.read_*'], readOnly: ['files.read_*'] },
|
|
1145
|
+
)
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
Credentials always come from your environment or config. The framework ships no default token for any server.
|
|
1149
|
+
|
|
1150
|
+
## The policy is the ceiling
|
|
1151
|
+
|
|
1152
|
+
An `exposeAsTool` descriptor executes **your** handler under **the caller's** subject, so an agent's ceiling is that subject's permissions by construction. An external MCP server has no such property — it runs elsewhere, with whatever credentials you gave it. So for external tools the policy *is* the ceiling, and it is deliberately stricter:
|
|
1153
|
+
|
|
1154
|
+
| | App tools (`appTools`) | External tools (`mcpToolset`) |
|
|
1155
|
+
|---|---|---|
|
|
1156
|
+
| Default exposure | Only descriptors annotated `exposeAsTool` | **None** — `allow` is required |
|
|
1157
|
+
| Allow / deny | `passesPolicy`, deny beats allow | The same function, same globs |
|
|
1158
|
+
| Read vs write | The descriptor's `kind` | **The app's `readOnly` list**, not the server's hint |
|
|
1159
|
+
| Writes | `includeWrites: true` | `includeWrites: true` |
|
|
1160
|
+
| Confirm | On for writes by default | On for writes by default |
|
|
1161
|
+
|
|
1162
|
+
Three consequences worth stating outright:
|
|
1163
|
+
|
|
1164
|
+
- **Omitting `allow` is refused, not defaulted.** A descriptor got a per-tool decision when somebody wrote `exposeAsTool` on it. Nobody in your repository wrote anything about a server's 94 tools, so naming what you allow is that decision.
|
|
1165
|
+
- **A server's `readOnlyHint` does not classify a tool.** The server is the untrusted party and can change the hint between two `tools/list` calls, so believing it would be a way to talk past `includeWrites: false`. Set `trustToolHints: true` if you want to delegate that judgement — explicitly, in one place a reviewer can find.
|
|
1166
|
+
- **The gate runs again inside every tool body**, so a tool spliced into the record after mount still cannot reach the server.
|
|
1167
|
+
|
|
1168
|
+
`toolset.specs` is a `SynthesizedTool[]` — the same inventory type app tools produce — so one confirm-UI covers both kinds. `toolset.dropped` lists everything the server advertised that did not mount, and why.
|
|
1169
|
+
|
|
1170
|
+
## Bounding an untrusted server
|
|
1171
|
+
|
|
1172
|
+
A server's tool names, descriptions, schemas and results all reach your model's context. Every one of them is bounded, and every bound is an option with a default and an environment override:
|
|
1173
|
+
|
|
1174
|
+
| Bound | Default | Env |
|
|
1175
|
+
|---|---|---|
|
|
1176
|
+
| `maxTools` | 64 | `VOLTRO_MCP_MAX_TOOLS` |
|
|
1177
|
+
| `maxDescriptionChars` | 1024 | `VOLTRO_MCP_MAX_DESCRIPTION_CHARS` |
|
|
1178
|
+
| `maxSchemaBytes` | 32 KiB | `VOLTRO_MCP_MAX_SCHEMA_BYTES` |
|
|
1179
|
+
| `maxResultBytes` | 256 KiB | `VOLTRO_MCP_MAX_RESULT_BYTES` |
|
|
1180
|
+
| `maxResponseBytes` | 4 MiB | `VOLTRO_MCP_MAX_RESPONSE_BYTES` |
|
|
1181
|
+
| `requestTimeoutMs` | 30 000 | `VOLTRO_MCP_TIMEOUT_MS` |
|
|
1182
|
+
|
|
1183
|
+
```ts
|
|
1184
|
+
yield* mcpToolset(server, {
|
|
1185
|
+
allow: ['github.get_*'],
|
|
1186
|
+
bounds: { maxTools: 12, maxResultBytes: 32 * 1024 },
|
|
1187
|
+
})
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
Alongside the numbers:
|
|
1191
|
+
|
|
1192
|
+
- **Tool names must be `[A-Za-z0-9_-]`.** Anything else is dropped rather than sanitized — a truncated name would not be the name you allowed.
|
|
1193
|
+
- **Descriptions and schemas are stripped of invisible characters** (zero-width spaces, bidi overrides, the Unicode tags block) before they reach the model. Those are how an injection hides from the human reviewing the same string.
|
|
1194
|
+
- **Every description carries a provenance prefix** telling the model the text is third-party, not an instruction from your app.
|
|
1195
|
+
- **Non-text results are described, not inlined** — a 6 MB base64 image does not buy a context window.
|
|
1196
|
+
- **The tool set is snapshotted at mount.** Nothing re-reads `tools/list` on its own; a server that renames or re-describes its tools between calls changes nothing until you call `refreshMcpToolset`.
|
|
1197
|
+
|
|
1198
|
+
## What this does not defend against
|
|
1199
|
+
|
|
1200
|
+
Stated plainly, because a bound you assume is worse than one you know you lack:
|
|
1201
|
+
|
|
1202
|
+
- **Instructions inside a description or a result.** They are bounded, sanitized and labelled, but a model may still choose to obey them. What actually contains the damage is the allowlist above: an injected "now call `admin_delete_all`" reaches a tool that was never mounted.
|
|
1203
|
+
- **A server that lies about a tool's effect**, or does something destructive inside a tool you allowed. The ceiling there is the credentials you gave the server — scope them.
|
|
1204
|
+
- **Argument exfiltration.** The allowlist bounds *which* tools run, not what the model puts in their arguments. A mounted external tool is a channel out of your process; do not mount one alongside tools that read secrets and expect the two not to meet.
|
|
1205
|
+
- **The transport target.** `url` / `command` are treated as app configuration. There is no SSRF guard or binary allowlist, because a legitimate deployment mounts an MCP server on a private address. If either can be influenced by user or model input in your app, gate it there.
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
|
|
1022
1209
|
---
|
|
1023
1210
|
|
|
1024
1211
|
<!-- source: en/ai/streaming.md -->
|
|
@@ -1046,6 +1233,10 @@ import { Schema } from 'effect'
|
|
|
1046
1233
|
|
|
1047
1234
|
export const supportRun = defineStream({
|
|
1048
1235
|
name: 'support.run',
|
|
1236
|
+
// Every call spends provider tokens, so this is metered work — guard it
|
|
1237
|
+
// rather than declaring it open, and rate-limit it before it faces the
|
|
1238
|
+
// internet (`@voltro/plugin-ratelimit`).
|
|
1239
|
+
guards: [{ scope: 'support:chat' }],
|
|
1049
1240
|
input: Schema.Struct({ message: Schema.String }),
|
|
1050
1241
|
element: AgentEvent,
|
|
1051
1242
|
})
|
|
@@ -1877,6 +2068,78 @@ Nothing about this needs a third party to operate an inference tier. It needs so
|
|
|
1877
2068
|
|
|
1878
2069
|
The Flow tab shows the queue: what is waiting and for how long, which calls have been waiting more than two minutes, which claims have a lease their dispatcher will never release, and the dispatcher's own last tick. A run parked on an offloaded call reads `suspended` in the run list with no step row yet, so this is the only view of the wait while it is happening.
|
|
1879
2070
|
|
|
2071
|
+
## Budget SUSPEND — stop spending without destroying the run
|
|
2072
|
+
|
|
2073
|
+
`requireAiBudget` above fails ONE call. That does stop the spend, and it does it
|
|
2074
|
+
by killing a durable run that may be nine steps in — the work is lost, and lost
|
|
2075
|
+
again on every retry until somebody raises the limit. A ceiling whose only
|
|
2076
|
+
expression is destruction gets set high, or turned off. `defineCostBudget` sits
|
|
2077
|
+
at the other extreme: it is an observability-grade signal over work that already
|
|
2078
|
+
happened, and never blocks anything.
|
|
2079
|
+
|
|
2080
|
+
The third answer is the one the workflow engine already knows how to do for a
|
|
2081
|
+
human: **suspend**. `aiStep` / `aiObjectStep` take a `budget`:
|
|
2082
|
+
|
|
2083
|
+
```ts
|
|
2084
|
+
import { aiStep } from '@voltro/ai/workflow'
|
|
2085
|
+
|
|
2086
|
+
const summary = yield* aiStep({
|
|
2087
|
+
name: 'summarise-thread',
|
|
2088
|
+
prompt: `Summarise:\n${thread}`,
|
|
2089
|
+
store: ctx.store,
|
|
2090
|
+
tenantId,
|
|
2091
|
+
budget: {
|
|
2092
|
+
limitUsd: 50,
|
|
2093
|
+
estimateUsd: 0.25,
|
|
2094
|
+
onExceeded: 'suspend',
|
|
2095
|
+
},
|
|
2096
|
+
})
|
|
2097
|
+
```
|
|
2098
|
+
|
|
2099
|
+
Over the cap, the run parks on a durable `_voltro_budget_holds` row, frees its
|
|
2100
|
+
worker, and resumes when the budget has headroom — then continues from where it
|
|
2101
|
+
stopped. Nothing is spent while it is held, and nothing is lost.
|
|
2102
|
+
|
|
2103
|
+
**The ordering is the feature.** The gate reads the *reservation counter* before
|
|
2104
|
+
the journaled step and before an offloaded call is enqueued — not a sum over
|
|
2105
|
+
`_voltro_ai_usage`, which by definition only knows about money already gone. On
|
|
2106
|
+
the suspend path no provider is contacted and no queue row exists for a
|
|
2107
|
+
dispatcher to pick up. Under the cap, `estimateUsd` is RESERVED atomically before
|
|
2108
|
+
the call, which is the difference between a ceiling and a speed bump.
|
|
2109
|
+
|
|
2110
|
+
**A release wakes a run; it does not authorise a spend.** Every wake re-reads the
|
|
2111
|
+
budget and parks again if it is still over, so an operator lifting the wrong
|
|
2112
|
+
hold — or a window rolling over for a tenant that immediately spends again —
|
|
2113
|
+
cannot spend through the ceiling. Three things can wake a hold:
|
|
2114
|
+
|
|
2115
|
+
- its own durable recheck clock (15 minutes by default, `recheckEveryMs`), so a
|
|
2116
|
+
tumbling window that rolls over on a clock nothing notifies us about is still
|
|
2117
|
+
noticed;
|
|
2118
|
+
- `releaseBudgetHolds({ store, budget, tenantId? })`, called from your own code
|
|
2119
|
+
— an admin mutation, or a subscriber on `defineCostBudget`'s `recovered`
|
|
2120
|
+
signal. The framework does not subscribe for you: whether a compute budget
|
|
2121
|
+
recovering should wake AI holds is an app decision, and the recheck clock
|
|
2122
|
+
already guarantees the run is not stranded either way;
|
|
2123
|
+
- its total timeout (`holdTimeoutMs`, 7 days), after which the run fails having
|
|
2124
|
+
spent nothing.
|
|
2125
|
+
|
|
2126
|
+
`onExceeded: 'fail'` is the default, so an existing `budget` behaves exactly like
|
|
2127
|
+
`requireAiBudget`. A compute budget opts in the same way:
|
|
2128
|
+
|
|
2129
|
+
```ts
|
|
2130
|
+
import { defineCostBudget } from '@voltro/runtime'
|
|
2131
|
+
|
|
2132
|
+
export default defineCostBudget({
|
|
2133
|
+
name: 'tenant-recompute-hourly',
|
|
2134
|
+
unit: 'recompute',
|
|
2135
|
+
limit: 100_000,
|
|
2136
|
+
window: '1h',
|
|
2137
|
+
onExceeded: 'suspend', // default 'observe' — signal only
|
|
2138
|
+
})
|
|
2139
|
+
```
|
|
2140
|
+
|
|
2141
|
+
Held runs are visible via `pendingBudgetHolds(store)`.
|
|
2142
|
+
|
|
1880
2143
|
## Deliberately your call
|
|
1881
2144
|
|
|
1882
2145
|
The toolkit prices + records + gates; a few things stay explicit by design:
|
|
@@ -1896,6 +2159,110 @@ For quota tied to billing TIERS (not a raw USD cap), see
|
|
|
1896
2159
|
|
|
1897
2160
|
|
|
1898
2161
|
|
|
2162
|
+
---
|
|
2163
|
+
|
|
2164
|
+
<!-- source: en/ai/prompt-versioning.md -->
|
|
2165
|
+
## Prompt versioning
|
|
2166
|
+
|
|
2167
|
+
_`definePrompt` makes a prompt an identified, versioned artefact — and stamps that identity onto the step row, the spend ledger and a `_voltro_prompts` table, so "which prompt produced this run" is a lookup._
|
|
2168
|
+
|
|
2169
|
+
Your schema is versioned. Your rows carry provenance. Your prompts — the part of an AI feature that changes weekly and is edited by whoever is nearest — were code with no identity at all.
|
|
2170
|
+
|
|
2171
|
+
`definePrompt` closes that asymmetry. It reuses the digest `aiStep` already recorded on step rows rather than inventing a second identity scheme.
|
|
2172
|
+
|
|
2173
|
+
## Declaring a prompt
|
|
2174
|
+
|
|
2175
|
+
```ts
|
|
2176
|
+
import { definePrompt } from '@voltro/ai'
|
|
2177
|
+
|
|
2178
|
+
export const triage = definePrompt({
|
|
2179
|
+
id: 'support.triage',
|
|
2180
|
+
system: 'You triage support tickets. Answer only with the category.',
|
|
2181
|
+
template: 'Ticket:\n{{body}}\n\nCategories: {{categories}}',
|
|
2182
|
+
label: 'v3-shorter-system',
|
|
2183
|
+
})
|
|
2184
|
+
```
|
|
2185
|
+
|
|
2186
|
+
- **`id`** is the stable identity across revisions, dotted like a descriptor tag.
|
|
2187
|
+
- **`template`** is a string with `{{name}}` placeholders — not a function. A function has no stable content to hash, so a function-built prompt could only be versioned by hashing its *output*, which would make every distinct customer message a new "version".
|
|
2188
|
+
- **`digest`** is computed from the template + system at definition time. It is the version, and it exists before any database does — a test, an eval, or a CLI can identify a prompt version with no connection.
|
|
2189
|
+
- **`label`** is metadata and deliberately **not** part of the digest, so renaming a revision does not fork it.
|
|
2190
|
+
|
|
2191
|
+
Render it to get the text plus a stamp:
|
|
2192
|
+
|
|
2193
|
+
```ts
|
|
2194
|
+
const rendered = yield* triage.render({ body: ticket.body, categories: 'hardware, software, billing' })
|
|
2195
|
+
// { promptId: 'support.triage', digest: 'sha256:…', prompt: 'Ticket:\n…', system: '…' }
|
|
2196
|
+
```
|
|
2197
|
+
|
|
2198
|
+
A missing variable **fails** rather than sending the literal `{{body}}` to a model.
|
|
2199
|
+
|
|
2200
|
+
## Provenance you get by using the primitive
|
|
2201
|
+
|
|
2202
|
+
Pass the rendered prompt straight to `aiStep` — the string form still works, it just carries no stamp:
|
|
2203
|
+
|
|
2204
|
+
```ts
|
|
2205
|
+
const result = yield* aiStep({
|
|
2206
|
+
name: 'triage-ticket',
|
|
2207
|
+
prompt: rendered,
|
|
2208
|
+
store: ctx.store,
|
|
2209
|
+
offload: true,
|
|
2210
|
+
})
|
|
2211
|
+
```
|
|
2212
|
+
|
|
2213
|
+
That one call now writes the same `promptId` + digest to three places:
|
|
2214
|
+
|
|
2215
|
+
| Where | What it answers |
|
|
2216
|
+
|---|---|
|
|
2217
|
+
| `_voltro_workflow_run_steps.input` | Which prompt version produced **this run** |
|
|
2218
|
+
| `_voltro_ai_usage` | What each prompt version **cost** |
|
|
2219
|
+
| `_voltro_prompts` | What the template **was**, at that version |
|
|
2220
|
+
|
|
2221
|
+
Nothing has to be remembered at call time, and offloaded calls are covered too — the stamp rides across the suspend on the queue row, so the dispatcher attributes the spend to the same version.
|
|
2222
|
+
|
|
2223
|
+
`recordPrompt: 'none'` still records the provenance. That is deliberate: the reason to record nothing about a prompt is that its *text* is sensitive, and an id plus a content digest is neither the text nor derivable from it. Suppressing the identity along with the content would mean the most privacy-conscious setting is also the one where you cannot tell which prompt version ran.
|
|
2224
|
+
|
|
2225
|
+
## Reading it back
|
|
2226
|
+
|
|
2227
|
+
```ts
|
|
2228
|
+
import { aiSpendUsd, promptVersionByDigest, promptVersionsFor } from '@voltro/ai'
|
|
2229
|
+
|
|
2230
|
+
// From a run's step row → the artefact.
|
|
2231
|
+
const version = yield* promptVersionByDigest(ctx.store, stepInput.promptVersion)
|
|
2232
|
+
version?.template // the template, as it was
|
|
2233
|
+
|
|
2234
|
+
// The history of one prompt, newest revision first.
|
|
2235
|
+
const history = yield* promptVersionsFor(ctx.store, 'support.triage')
|
|
2236
|
+
|
|
2237
|
+
// Did revision 4 cost more than revision 3?
|
|
2238
|
+
const spend = yield* aiSpendUsd(ctx.store, { promptDigest: version.digest })
|
|
2239
|
+
```
|
|
2240
|
+
|
|
2241
|
+
`recordPromptVersion` assigns the human-facing `revision` (1, 2, 3 …) on first sight of a `(promptId, digest)` pair and is idempotent afterwards. Under a two-replica race two different versions can land on the same revision **number** — accepted deliberately: the digest is the exact identity, the revision is a label for humans, and order by `firstSeenAt` when you need the true sequence.
|
|
2242
|
+
|
|
2243
|
+
## Retention
|
|
2244
|
+
|
|
2245
|
+
`_voltro_prompts` is bounded by the standard retention sweep on `lastUsedAt`, defaulting to **365 days** and tunable with `VOLTRO_AI_PROMPTS_TTL_HOURS`. The table is self-healing under it: a version that ages out is one nothing has run in a year, and the next run re-registers it.
|
|
2246
|
+
|
|
2247
|
+
The registration is `framework`-precedence, so an app that registers its own window for the table wins without having to know the framework's exists.
|
|
2248
|
+
|
|
2249
|
+
## The table
|
|
2250
|
+
|
|
2251
|
+
```text
|
|
2252
|
+
_voltro_prompts
|
|
2253
|
+
promptId stable identity across revisions
|
|
2254
|
+
digest content digest of template + system — the version
|
|
2255
|
+
revision human-facing counter within a promptId
|
|
2256
|
+
label the author's name for this revision
|
|
2257
|
+
template the TEMPLATE (code) — never a rendered prompt (data)
|
|
2258
|
+
system
|
|
2259
|
+
firstSeenAt / lastUsedAt
|
|
2260
|
+
```
|
|
2261
|
+
|
|
2262
|
+
Storing the template is safe precisely because it is code — it is in your repository already. The rendered prompt, which may contain a customer's message, is never written here.
|
|
2263
|
+
|
|
2264
|
+
|
|
2265
|
+
|
|
1899
2266
|
---
|
|
1900
2267
|
|
|
1901
2268
|
<!-- source: en/ai/data-copilot.md -->
|
|
@@ -1929,6 +2296,9 @@ import { Schema } from 'effect'
|
|
|
1929
2296
|
|
|
1930
2297
|
export const ask = defineAction({
|
|
1931
2298
|
name: 'copilot.ask',
|
|
2299
|
+
// A model-proposed read, executed as the caller. The grammar bounds WHAT can
|
|
2300
|
+
// be asked; this bounds WHO may ask.
|
|
2301
|
+
guards: [{ scope: 'copilot:ask' }],
|
|
1932
2302
|
input: Schema.Struct({ question: Schema.String }),
|
|
1933
2303
|
output: Schema.Union(
|
|
1934
2304
|
Schema.Struct({ ok: Schema.Literal(true), rows: Schema.Array(Schema.Record({ key: Schema.String, value: Schema.Unknown })) }),
|