--- name: taruvi-app-developer description: > 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", "Taruvi MCP". Skip for Refine frontend work — use `taruvi-refine-providers` instead. license: Apache-2.0 compatibility: > Requires the Taruvi MCP server for provisioning tasks. Python function bodies run inside the Taruvi function runtime (Celery workers) with an injected, pre-authenticated `sdk_client`. metadata: author: EOX Vantage version: "2.0.0" organization: Taruvi --- # 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`. ## MCP tool names This skill describes MCP **operations** ("save a table schema", "run a function"), not tool names — the server's tool names and argument shapes change over time. Pick the tool from the connected server's tool list whose description matches the operation, and follow that tool's description and input schema. When the live tool description disagrees with this skill, the tool description wins. If no tool covers an operation, say so and use the SDK, REST API, or Console instead of guessing a name. ## 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`](references/when-not-to-use-functions.md). ## Workflow 1. Confirm the MCP connection: list the apps this connection covers and check it's the intended site and app. 2. 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). 3. 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). 4. For MCP work, open [`mcp-tool-quickref.md`](references/mcp-tool-quickref.md) and the task-specific reference below. 5. For destructive ops, follow the destructive-op protocol (below). 6. Verify by re-reading the resource (the table schema, the policy, etc.) and executing functions / analytics queries end-to-end before reporting done. ## Reference index | Task | Read | |---|---| | MCP operations and gotchas | [`mcp-tool-quickref.md`](references/mcp-tool-quickref.md) | | Frictionless schema (FKs, indexes, hierarchy, graph) | [`datatable-schema-patterns.md`](references/datatable-schema-patterns.md) | | Cerbos policy authoring | [`cerbos-policy-cookbook.md`](references/cerbos-policy-cookbook.md) — or the server's policy docs (MCP resources or docs tool) | | Secrets and custom secret types | [`secrets-and-types.md`](references/secrets-and-types.md) | | Audited raw SQL | [`raw-sql-safety.md`](references/raw-sql-safety.md) | | Analytics queries (registration + Jinja2 params) | [`analytics-queries.md`](references/analytics-queries.md) | | Filter operators / query capabilities | [`backend-capabilities.md`](references/backend-capabilities.md) | | Function runtime contract, modes, triggers | [`function-authoring.md`](references/function-authoring.md) | | `sdk_client` modules and methods | [`function-sdk-reference.md`](references/function-sdk-reference.md) | | Event triggers, CEL filters | [`function-events.md`](references/function-events.md) | | Worked function examples (8 scenarios) | [`function-scenarios.md`](references/function-scenarios.md) | | Cross-layer worked features | [`feature-workflow-examples.md`](references/feature-workflow-examples.md) | | Integration pitfalls | [`integration-pitfalls.md`](references/integration-pitfalls.md) | | Frontend Worker deploy | [`frontend-worker-deploy.md`](references/frontend-worker-deploy.md) | | Export backend config | [`backend-export.md`](references/backend-export.md) | | Env var setup | [`env-setup.md`](references/env-setup.md) | ## Non-negotiables Most of these are wired into the references too — listed here because skipping them causes real bugs. 1. **Trust the MCP tool, not memory.** Tool responses carry the IDs/slugs/status you need next. Don't invent endpoints or method names. 2. **Saving a table schema drops fields missing from the payload.** Always read the current schema first, then send the full preserved field list. 3. **Saving a policy replaces it — does not merge.** Read the existing policy first, mutate, and send the full body back. 4. **Function signature is exactly `def main(params, user_data, sdk_client):`.** Any deviation → immediate `SandboxError`. 5. **Never re-authenticate `sdk_client`.** It's pre-authenticated. No `client.auth()`, no `client.login()`, no API keys passed in. 6. **Never hardcode secrets.** Use `sdk_client.secrets.get("KEY")`. 7. **Return JSON-serializable values from functions.** `datetime`, `set`, `Decimal`, and custom classes crash on return — convert first. 8. **Tasks >30s need `is_async=True`.** Sync calls time out at 30s and leave UI spinners stuck. 9. **Use `log()`, not `print()`.** `print()` is unstructured stdout and not queryable. 10. **Frontend multi-resource cascades are bugs.** Move them to a function — browser navigation mid-operation leaves the DB inconsistent. ## Destructive-op protocol Applies to: deleting tables, records, functions, roles, secret types, or queries; saving a table schema that removes or narrows a field; saving over an existing policy; disabling a policy; replacing the user attributes schema; revoking roles; and any raw SQL (if the server offers it) containing `DROP`, `TRUNCATE`, destructive `ALTER`, or `DELETE` without `WHERE`. 1. **Plan** — inspect current state (read the table schema, the policy, …) and state what will be deleted/replaced and what depends on it. 2. **Validate** — surface the blast radius in plain language ("This will drop `orders` and 3 dependent FKs in `invoices`"). Get explicit user confirmation. 3. **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__` (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: 1. Run the function or analytics query through the MCP server with realistic sample params. 2. Inspect the actual response shape. 3. 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.