Taruvi app developer (backend)
Single backend skill for Taruvi. Covers two layers:
- Control plane (MCP) — provisioning datatables, policies, roles, buckets, secrets, function metadata, analytics queries, audited raw SQL.
- Data plane (function runtime) — Python bodies with signature
def main(params, user_data, sdk_client) running on Celery workers with a pre-authenticated sdk_client.
If the task is wiring React/Refine providers or hooks on the frontend, switch to taruvi-refine-providers.
Decision: function or just MCP provisioning?
Provisioning alone is enough for: schema changes, policy/role/user management, bucket creation, secret management, registering analytics queries, one-shot admin SQL.
You need a function body when the task involves any of:
- 2+ resources at runtime (DB + storage, users + DB, etc.)
- Event triggers (
RECORD_CREATE, RECORD_UPDATE, RECORD_DELETE, POST_USER_CREATE, …)
- Schedule / cron
- External API call with a stored secret
- Long-running work (>30s →
async_mode=True)
- Public unauthenticated endpoint (
is_public=True)
- Authorization gate beyond plain Cerbos resource policy
- Function-to-function pipeline
When in doubt, see when-not-to-use-functions.md.
Workflow
- Read
architecture-overview.md once for the runtime split; for greenfield apps also scaffold an AGENTS.md from agents-md-template.md.
- Decide function vs provisioning (above). Before writing function code, read
function-authoring.md and the relevant section of function-sdk-reference.md.
- For MCP work, open
mcp-tool-quickref.md and the task-specific reference below.
- For destructive ops, follow the destructive-op protocol (below).
- Verify by re-reading the resource (
get_datatable_schema, manage_policies(action="get"), etc.) and executing functions / analytics queries end-to-end before reporting done.
Reference index
| Task |
Read |
| MCP tool signatures |
mcp-tool-quickref.md |
| Frictionless schema (FKs, indexes, hierarchy, graph) |
datatable-schema-patterns.md |
| Cerbos policy authoring |
cerbos-policy-cookbook.md — or get_ai_docs(category="policies", topic="guide") |
| Secrets and custom secret types |
secrets-and-types.md |
| Audited raw SQL |
raw-sql-safety.md |
| Analytics queries (registration + Jinja2 params) |
analytics-queries.md |
| Filter operators / query capabilities |
backend-capabilities.md |
| Function runtime contract, modes, triggers |
function-authoring.md |
sdk_client modules and methods |
function-sdk-reference.md |
| Event triggers, CEL filters |
function-events.md |
| Worked function examples (8 scenarios) |
function-scenarios.md |
| Cross-layer worked features |
feature-workflow-examples.md |
| Integration pitfalls |
integration-pitfalls.md |
| Frontend Worker deploy |
frontend-worker-deploy.md |
| Export backend config |
backend-export.md |
| Env var setup |
env-setup.md |
Non-negotiables
Most of these are wired into the references too — listed here because skipping them causes real bugs.
- Trust the MCP tool, not memory. Tool responses carry the IDs/slugs/status you need next. Don't invent endpoints or method names.
create_update_schema drops fields missing from the payload. Always get_datatable_schema first, then send the full preserved field list.
manage_policies(action="create_update") replaces — does not merge. Get the existing policy first, mutate, and send the full body back.
- Function signature is exactly
def main(params, user_data, sdk_client):. Any deviation → immediate SandboxError.
- Never re-authenticate
sdk_client. It's pre-authenticated. No client.auth(), no client.login(), no API keys passed in.
- Never hardcode secrets. Use
sdk_client.secrets.get("KEY").
- Return JSON-serializable values from functions.
datetime, set, Decimal, and custom classes crash on return — convert first.
- Tasks >30s need
is_async=True. Sync calls time out at 30s and leave UI spinners stuck.
- Use
log(), not print(). print() is unstructured stdout and not queryable.
- Frontend multi-resource cascades are bugs. Move them to a function — browser navigation mid-operation leaves the DB inconsistent.
Destructive-op protocol
Applies to: delete_datatable (especially force=True), manage_policies(action="create_update") on an existing policy, user_attributes_schema(action="update"), manage_secret_types(action="delete"), manage_function(action="delete"), manage_roles(action="delete"), and any execute_raw_sql containing DROP, TRUNCATE, destructive ALTER, or DELETE without WHERE.
- Plan — inspect current state (
get_datatable_schema, manage_policies(action="get"), …) and state what will be deleted/replaced and what depends on it.
- Validate — surface the blast radius in plain language ("This will drop
orders and 3 dependent FKs in invoices"). Get explicit user confirmation.
- Execute — only after confirmation. Report the tool's response verbatim.
Test users — always
After provisioning, create test users so the user can verify the app works:
- With access control: one test user per role, named
qa_<role_slug>_<YYYYMMDD> (e.g., qa_warehouse_staff_20260422), strong 12+ char password (mixed case + digit + symbol). Report all usernames and passwords back to the user.
- No access control: one default super-admin test user, password reported.
- Include cleanup guidance: deactivate/delete the
qa_* users after validation, or rotate their passwords.
Verify by executing — always
Before reporting a function or analytics query done:
- Run
execute_function(function_slug=..., params={...}) or execute_query(query_slug=..., params={...}) with realistic sample params.
- Inspect the actual response shape.
- Confirm any frontend code uses the exact field names and structure returned — fix the frontend if they don't match.
Drift check
node scripts/check-versions.js warns when pinned SDK/provider versions drift from the latest on PyPI/npm.
1---2name: taruvi-app-developer3description: Backend work on a Taruvi app: provisioning datatables, Cerbos policies, roles, users, buckets, secrets, analytics queries, or raw SQL via the Taruvi MCP server; and authoring Python function bodies that run in the Taruvi function runtime (`def main(params, user_data, sdk_client)`) for multi-resource cascades, event/cron handlers, public webhooks, and external API calls. Triggers: "create a datatable", "Frictionless schema", "Cerbos policy", "serverless function", "scheduled job", "analytics query", "sdk_client", `manage_function`, `execute_raw_sql`. Skip for Refine frontend work — use `taruvi-refine-providers` instead.4license: Apache-2.05---67# Taruvi app developer (backend)89Single backend skill for Taruvi. Covers two layers:1011- **Control plane (MCP)** — provisioning datatables, policies, roles, buckets, secrets, function metadata, analytics queries, audited raw SQL.12- **Data plane (function runtime)** — Python bodies with signature `def main(params, user_data, sdk_client)` running on Celery workers with a pre-authenticated `sdk_client`.1314If the task is wiring React/Refine providers or hooks on the frontend, switch to `taruvi-refine-providers`.1516## Decision: function or just MCP provisioning?1718Provisioning alone is enough for: schema changes, policy/role/user management, bucket creation, secret management, registering analytics queries, one-shot admin SQL.1920You need a **function body** when the task involves any of:2122- 2+ resources at runtime (DB + storage, users + DB, etc.)23- Event triggers (`RECORD_CREATE`, `RECORD_UPDATE`, `RECORD_DELETE`, `POST_USER_CREATE`, …)24- Schedule / cron25- External API call with a stored secret26- Long-running work (>30s → `async_mode=True`)27- Public unauthenticated endpoint (`is_public=True`)28- Authorization gate beyond plain Cerbos resource policy29- Function-to-function pipeline3031When in doubt, see [`when-not-to-use-functions.md`](references/when-not-to-use-functions.md).3233## Workflow34351. Read [`architecture-overview.md`](references/architecture-overview.md) once for the runtime split; for greenfield apps also scaffold an `AGENTS.md` from [`agents-md-template.md`](references/agents-md-template.md).362. Decide function vs provisioning (above). Before writing function code, read [`function-authoring.md`](references/function-authoring.md) and the relevant section of [`function-sdk-reference.md`](references/function-sdk-reference.md).373. For MCP work, open [`mcp-tool-quickref.md`](references/mcp-tool-quickref.md) and the task-specific reference below.384. For destructive ops, follow the destructive-op protocol (below).395. Verify by re-reading the resource (`get_datatable_schema`, `manage_policies(action="get")`, etc.) and executing functions / analytics queries end-to-end before reporting done.4041## Reference index4243| Task | Read |44|---|---|45| MCP tool signatures | [`mcp-tool-quickref.md`](references/mcp-tool-quickref.md) |46| Frictionless schema (FKs, indexes, hierarchy, graph) | [`datatable-schema-patterns.md`](references/datatable-schema-patterns.md) |47| Cerbos policy authoring | [`cerbos-policy-cookbook.md`](references/cerbos-policy-cookbook.md) — or `get_ai_docs(category="policies", topic="guide")` |48| Secrets and custom secret types | [`secrets-and-types.md`](references/secrets-and-types.md) |49| Audited raw SQL | [`raw-sql-safety.md`](references/raw-sql-safety.md) |50| Analytics queries (registration + Jinja2 params) | [`analytics-queries.md`](references/analytics-queries.md) |51| Filter operators / query capabilities | [`backend-capabilities.md`](references/backend-capabilities.md) |52| Function runtime contract, modes, triggers | [`function-authoring.md`](references/function-authoring.md) |53| `sdk_client` modules and methods | [`function-sdk-reference.md`](references/function-sdk-reference.md) |54| Event triggers, CEL filters | [`function-events.md`](references/function-events.md) |55| Worked function examples (8 scenarios) | [`function-scenarios.md`](references/function-scenarios.md) |56| Cross-layer worked features | [`feature-workflow-examples.md`](references/feature-workflow-examples.md) |57| Integration pitfalls | [`integration-pitfalls.md`](references/integration-pitfalls.md) |58| Frontend Worker deploy | [`frontend-worker-deploy.md`](references/frontend-worker-deploy.md) |59| Export backend config | [`backend-export.md`](references/backend-export.md) |60| Env var setup | [`env-setup.md`](references/env-setup.md) |6162## Non-negotiables6364Most of these are wired into the references too — listed here because skipping them causes real bugs.65661. **Trust the MCP tool, not memory.** Tool responses carry the IDs/slugs/status you need next. Don't invent endpoints or method names.672. **`create_update_schema` drops fields missing from the payload.** Always `get_datatable_schema` first, then send the full preserved field list.683. **`manage_policies(action="create_update")` replaces — does not merge.** Get the existing policy first, mutate, and send the full body back.694. **Function signature is exactly `def main(params, user_data, sdk_client):`.** Any deviation → immediate `SandboxError`.705. **Never re-authenticate `sdk_client`.** It's pre-authenticated. No `client.auth()`, no `client.login()`, no API keys passed in.716. **Never hardcode secrets.** Use `sdk_client.secrets.get("KEY")`.727. **Return JSON-serializable values from functions.** `datetime`, `set`, `Decimal`, and custom classes crash on return — convert first.738. **Tasks >30s need `is_async=True`.** Sync calls time out at 30s and leave UI spinners stuck.749. **Use `log()`, not `print()`.** `print()` is unstructured stdout and not queryable.7510. **Frontend multi-resource cascades are bugs.** Move them to a function — browser navigation mid-operation leaves the DB inconsistent.7677## Destructive-op protocol7879Applies to: `delete_datatable` (especially `force=True`), `manage_policies(action="create_update")` on an existing policy, `user_attributes_schema(action="update")`, `manage_secret_types(action="delete")`, `manage_function(action="delete")`, `manage_roles(action="delete")`, and any `execute_raw_sql` containing `DROP`, `TRUNCATE`, destructive `ALTER`, or `DELETE` without `WHERE`.80811. **Plan** — inspect current state (`get_datatable_schema`, `manage_policies(action="get")`, …) and state what will be deleted/replaced and what depends on it.822. **Validate** — surface the blast radius in plain language ("This will drop `orders` and 3 dependent FKs in `invoices`"). Get explicit user confirmation.833. **Execute** — only after confirmation. Report the tool's response verbatim.8485## Test users — always8687After provisioning, create test users so the user can verify the app works:8889- **With access control:** one test user per role, named `qa_<role_slug>_<YYYYMMDD>` (e.g., `qa_warehouse_staff_20260422`), strong 12+ char password (mixed case + digit + symbol). Report all usernames and passwords back to the user.90- **No access control:** one default super-admin test user, password reported.91- **Include cleanup guidance:** deactivate/delete the `qa_*` users after validation, or rotate their passwords.9293## Verify by executing — always9495Before reporting a function or analytics query done:96971. Run `execute_function(function_slug=..., params={...})` or `execute_query(query_slug=..., params={...})` with realistic sample params.982. Inspect the actual response shape.993. Confirm any frontend code uses the exact field names and structure returned — fix the frontend if they don't match.100101## Drift check102103`node scripts/check-versions.js` warns when pinned SDK/provider versions drift from the latest on PyPI/npm.