Your agent can write the CRM. It can't approve the discount.
The agent cannot approve
People ask this as: can an AI coding agent approve a deal or a discount for me
Two refusals, asserted in two different places, and which is which matters. The renewal boundary has its own named test in tests/workflow.test.js: 'approval workflow rejects an agent pretending to make the human decision'. The discount boundary is equally real but is asserted inside a composite test in tests/commercial-e2e.test.js, where an agent actor calling quote.approve is refused 403 HUMAN_APPROVAL_REQUIRED. Both hold against an honest agent and neither holds against an attacker: the actor is asserted in a header and never authenticated.
Where this stops
Read this before the rest of the page. Every line below is a thing this does not do.
- It is not authentication. The actor is asserted in a header, never verified, so the refusal holds against an honest agent and not against an attacker who can reach the port.
- The discount refusal has no named test of its own. It is asserted inside a composite commercial end-to-end test, and extracting it is recorded as pending work rather than described as done.
- requiredApprovalKey is a label, not a permission. Nothing checks that the human who approved was a sales manager, because there are no roles to check.
- The renewal policy is proven on the built-in Opportunity object at a single threshold value. It is not a general approval engine over arbitrary objects.
- Refusing the decision is not refusing the work. An agent may create the quote, add lines, submit it, trigger the policy and read every piece of evidence — it may not be the human at the end.
- The framework decides who may decide, not whether the decision was right. There is no second signature, no review of the human's judgement and no way to undo an approval other than a new version.
- No approval is ever raised by the passage of time. Without a scheduler, a renewal notice period fires nothing.
No authentication, tenancy or RBAC. The server is local-development-only. An actor header is an assertion, not an identity. Do not expose it to a network. Every claim and every limitation is on one page.
The renewal refusal, and the test that asserts it
ADR-003 states the policy: moving a renewal worth at least €50,000 to Proposal creates an approval request and moves the opportunity to Approval Pending. An AI model may explain or recommend, and it does not override that. Commercial policy is deterministic code rather than a model's judgement (C-03).
The assertion is the named test 'approval workflow rejects an agent pretending to make the human decision' in tests/workflow.test.js. It runs request-opportunity-stage-change with an agent actor, takes the pending approval that policy created, then runs decide-opportunity-approval with the same agent actor and asserts the rejection names an explicit human actor. It then asserts what did not happen: the approval is still pending and the opportunity is still in approval_pending. A refusal that left the state changed would not be a refusal (C-04).
The discount refusal, and exactly where it lives
ADR-016 states the same boundary where the money is: the approve and reject actions on a quote accept only actor.type === 'user', and an agent actor is refused 403 HUMAN_APPROVAL_REQUIRED (C-21). One decision is allowed per quote version — a second is 409 ALREADY_DECIDED — the decision and the quote lifecycle commit atomically, and concurrent approve and reject calls resolve to exactly one winner.
That refusal is asserted inside the composite test 'commercial e2e: approval boundary, revise/version-2, concurrency, fault injection, provider failures, drift' in tests/commercial-e2e.test.js. It does not have a named test of its own yet. GO_TO_MARKET.md §2.4 records this as a standing correction risk and as a pending move: extract it, so the strongest sentence this project owns cites a test name rather than a line inside a longer scenario. Until that lands, a headline that says discount must cite tests/commercial-e2e.test.js and not the renewal test.
Why it is a test and not a convention
A test count says how much was written, not what would have to go wrong for a test to stay green. The falsification kit answers the second question by removing rules on purpose and reporting which named test caught each one. The approval-actor-guard mutation removes the rule that a commercial approval decision requires a human actor — the failure it models is an agent recording the human decision as if it were the human — and tests/workflow.test.js is what catches it. The approval-threshold-boundary mutation removes the rule that a renewal exactly at the threshold requires approval.
Five mutations run in about two seconds and each names its catcher. A mutation that survives is reported as a gap in the tests, and a mutation whose target code is gone is reported as stale; both exit non-zero, because a falsification kit that has quietly stopped aiming at anything goes on printing reassurance (C-23, docs/FALSIFY.md).
What the decision is made of
The discount policy is a versioned code-first definition carrying a name, a version, a label, a declared config and an evaluate function. Thresholds live in the config, which is fingerprinted and passed frozen into every evaluation. Evaluate must return synchronously one of auto_approve, approval_required or reject; a Promise or an out-of-contract result is refused. The decision, the policy name, the version and the fingerprint are stored on the immutable quote version, so a quote decided last quarter stays explainable after policy v2 ships (C-08).
auto_approve creates no approval record, because a record with nobody's decision in it is evidence of nothing. approval_required creates exactly one approval, unique per version in the database, and parks the quote in pending_approval. Every mutation along that path goes through a module service or a named workflow and leaves an audit event and a step-level trace behind it (C-16).
Where the boundary stops
The actor is an assertion, not an identity. There is no authentication, no tenancy and no RBAC, and an actor header is a claim the caller makes about itself (L-01). The boundary therefore holds against an honest agent that says what it is, and not against anyone with network access who says otherwise. The server is local-development-only for exactly this reason.
The approval key is a label. The commercial test asserts requiredApprovalKey equals 'sales-manager' with the comment that this is a LABEL, not enforced security — real Sales-Manager or Finance role enforcement needs authentication, tenancy and roles enforced in services, and none of that exists. Obtaining the required commercial approval on a discount is 'partially supported' in the catalogue (JTBD-CO-04) for this reason, and enforcing team or tenant permissions is not supported at all (JTBD-15).
The renewal rule is proven on one built-in object with one threshold value. Managing a custom object with an approval rule is 'partially supported' (JTBD-06). And nothing raises an approval because time passed: there is no scheduler, no delayed workflow and no queue, so every approval in this system starts from somebody or something asking for a state change (L-04).
The evidence this page rests on
Claims and limitations are printed from site/claims.json word for word. Job statuses come from docs/benchmarks/jobs.json; a job with no page of its own is listed with its status rather than linked.
Claims
- C-04 The agent cannot approve on the human's behalf. A test asserts the refusal, so the boundary is a property of the system rather than a promise in a README.
LimitThe actor is asserted, not authenticated: there is no auth, tenancy or RBAC, and an actor header is not identity. This holds a boundary against an honest agent, not against an attacker with network access.
- C-03 Commercial policy is deterministic code, not a model's judgement: a renewal at or above the threshold stops and waits for a named human.
LimitProven for the built-in renewal object and its single value threshold. A general policy engine over arbitrary custom objects does not exist.
- C-21 The same refusal holds where the money is: an agent actor asking to approve a discounted quote is refused with a 403, and only a human user actor can decide.
LimitThe assertion lives inside a composite end-to-end test rather than a test named for it, so the citation is a file and a line rather than a test name. Extracting it into a named test is tracked in docs/strategy/GO_TO_MARKET.md; until then, cite the line.
- C-23 Five rules can be removed on purpose in one command, and the suite catches every one in about two seconds — naming the test that caught it. Anything that survives is reported as a gap, not omitted.
LimitIt falsifies six named rules, not the claims in this ledger, and it proves only that a test holds each one — a rule that is wrong but faithfully defended passes every mutation. It is not mutation testing: nothing is generated or sampled, and no score is derived.
- C-08 Quotes price on the server from a catalog — one-time and recurring, flat, per-unit, volume and graduated tiers — and freeze into an immutable version when a discount goes for approval.
LimitCatalog sync runs against a fixture provider; no real external catalog (Stripe, Zuora, ERP) is connected. Money is integer cents with no FX — currencies are never summed.
- C-16 Every mutation goes through a module service or a named workflow, and leaves an audit event and a step-level trace behind it.
LimitAudit records what the process did under an asserted actor. It is not a tamper-evident or externally attestable log, and it is not a compliance control.
Limitations
- L-01 No authentication, tenancy or RBAC. The server is local-development-only. An actor header is an assertion, not an identity. Do not expose it to a network.
- L-04 No scheduler, no task engine, no reminders. One follow-up Task is created inside lead qualification. There is no recurring work, no delayed workflow, no queue — so nothing fires on a renewal notice period either.
Jobs it covers
- JTBD-02 Request commercial approval on a deal — validated end to end
- JTBD-CO-03 Request a discount under a deterministic policy — validated end to end
- JTBD-CO-04 Obtain required commercial approval on a discount — partially supported
- JTBD-06 Manage a custom object with an approval rule — partially supported
- JTBD-15 Enforce team / tenant permissions — not supported
- JTBD-LI-06 Manually reassign with permission and reason — not supported