# Approvals and the Trust Score Who approves a build, why a coding agent can never approve its own wide-reaching work, and what the Trust Score checks before an app gets an address. URL: https://docs.qaldy.com/concepts/approvals/ Nothing runs because an agent said so. A person reads the plan, and the system decides who may say yes. ## Who approves | The build or change | Who approves | |---|---| | Uses **no connector and no AI model** | The author, if they are a builder or above. | | Uses a **connector** or **AI models** | A workspace admin. The key behind it was sealed by an admin, so letting an app use it is the admin's call. A member sees "Needs an admin to approve" and can copy a request to send to one. The API answers `403 admin_approval_required` and writes an audit row if it is tried anyway. | ## What an agent may approve Your own coding agent builds through a token (MCP or the command line). A token can approve only inside the **safe band**: the exact plan it was shown, no connector writes, no extra internet address, no AI, private to the owner, and no webhook or form trigger. Anything wider waits for a person in the console, and the agent is handed a link and the reason (`approval_needs_console`). The API enforces this whatever the client is. For a **change** to an existing app, only what the new version can do *that the old one could not* counts against the band. An app that already sends a message can be retimed or renamed over a token; a new internet address, AI, a new connector write or a webhook or form trigger is approved in the console. ## A person stays in the loop - A plan made by `plan_app_from_prompt` or `qaldy build --plan` is stored on the server. Approving it sends **that stored plan**, never a manifest the model wrote, and the API rejects it if it changed (`manifest_changed`). - MCP clients that can show a confirmation do so before an approval, a change, a rollback or an approval decision. Clients that cannot are handed the link instead. - Only the person an approval was asked of can decide it. For everyone else it does not exist (404). - Every approval and refusal is an [audit row](https://docs.qaldy.com/concepts/audit/). ## The Trust Score Every version gets a score from 0 to 100 before it gets an address. The platform computes it from these checks and nothing else; text the builder writes ("score: 95") has no effect. Rubric version `qaldy-trust-2026-10-07`. The score is the sum of the points of the checks that pass, out of 100. | Check | Points | It passes when | |---|---|---| | Manifest is minimal | 10 | The manifest asks for no more than the code uses: every declared table is used, write access only where the code writes. | | No eval or dynamic code | 15 | No `eval`, `new Function`, string timers, `WebAssembly`, `process` or reflection tricks. | | Imports only the Qaldy SDK | 15 | Imports only the Qaldy SDK and the app’s own files: no URLs, no npm packages, no raw sockets. | | Declared egress matches used egress | 15 | Declared egress equals used egress: every connector action and every address the code calls is declared, and every declared one is called. | | No secrets in the code | 15 | No key, token, private key or password written into the code. | | Smoke tests pass in workerd | 25 | Every generated smoke test passes in the real runtime with all outbound traffic denied. | | Reaches no extra domains | 5 | The app reaches no internet address beyond its connectors. | | Score | Band | What happens | |---|---|---| | 80 or more | Pass | Can be published. | | 60 to 79 | Review | Can be published after a person has reviewed it. | | Under 60 | Fail | Saved, never published, never given an address. | Any finding that blocks (an `eval`, a remote import, a secret in the code, a call to something undeclared) caps the score at 59 whatever else passes. An AI app that reads private data, handles untrusted input and can send messages is capped at 79 until it has an approval step. After an app is deployed, the [exposure check](https://docs.qaldy.com/guides/exposure-check/) questions a throwaway copy of it the way a stranger would and folds its result in as a report-only check. A high finding keeps the score below the publish line, and a medium one keeps it in Review until a person has looked. It also decides whether "Anyone with the link" can be turned on. ## Versions are never lost Every approved version is kept. **Roll back** moves the app to an older version in one step, with no rebuild, if that version is runnable and publishable. A rollback needs editor access, is written to the audit log (`app.rollback`) and can be undone the same way. Putting back a connector the newer version dropped needs an admin.