ADR 0001: Scope boundaries
Veefore is identity, authority, policy and enforcement. The adjacent products it deliberately is not, and the build gate that keeps it that way.
#Context
An agent authorization layer sits next to several much larger product categories. If an agent can be authorized to issue a refund, it is a short step to holding the balance, a shorter step to processing the payment, and a shorter one still to recording the transaction.
Each step is individually reasonable and collectively fatal. A product that is an authorization layer and a wallet and a ledger is not better at either; it is a different, larger, more regulated product with a confused security model — and it inherits custody obligations that have nothing to do with deciding whether an agent may act.
The pressure is real: "we already evaluate the amount, we may as well record the transaction" is a sentence somebody will say.
#Decision
Veefore is identity, authority, policy and enforcement infrastructure. The following do not exist, as code, schema, routes, services or UI:
wallet · marketplace · agent commerce · payment processing · settlement · payouts · disbursement · financial transaction ledger · smart contracts · merchant acquiring · agent purchasing · marketplace reviews
amount exists in exactly two roles: a policy input (a rule tests it, a
delegation caps it, a risk signal compares it against history) and recorded
decision context (the action record keeps what the agent asked to do).
The list is published, unauthenticated, at GET /v1/meta/info under
out_of_scope, so an integrator can check it against the deployment they are
talking to rather than against a document.
And it is enforced in CI. npm run scope:audit is a stage of
npm run verify.
#Alternatives
Document the boundary and rely on review. This is what most products do, and
it fails on a long enough timeline. A reviewer who has not read the boundary
document approves a settlements table because the PR it arrived in was about
something else.
Enforce only by naming. A keyword scan catches WalletService and
/v1/payments. It does not catch a wallet implemented without the word, which
is exactly what someone would build after the first rejection.
Allow it behind a feature flag. A flag means the code exists, the schema exists, and the tests exist. The scope boundary would then be a runtime setting, and runtime settings get turned on.
Keep it out and allow amounts nowhere. Then a policy cannot express "refunds above ₹1,000 need a human", which is the single most useful rule anyone writes. The product becomes unable to do its job in order to prove a point.
#Consequences
The audit has two independent checks, because naming alone is evadable.
Naming: table names, service classes, route paths, identifiers, nav labels. Comments and prose that state the boundary are allowed — a negated mention is documentation, not an implementation — which is how the scope page can exist.
Shape: no migration may declare balance, available_balance,
ledger_balance or funds. And any table with an amount column must be on an
explicit allow-list of policy and decision tables — actions, approvals,
delegations, delegation_constraints, policy_versions,
policy_test_cases, risk_events, plus Veefore's own plans,
subscriptions, usage_records.
An amount column on a table that is not a policy or decision table fails the build with that table named. A wallet could be written without the word "wallet"; it could not be written without somewhere to put a balance.
Customers build the excluded parts themselves, and register them as tools. That is the intended shape: your refund service is a tool, your ledger is yours, your payment provider is yours. Veefore decides whether the agent may call them and records what it decided.
The division of labour has to be explained repeatedly, because "it evaluates amounts but moves no money" is a distinction people have to be walked through once. The published exclusion list exists partly to shorten that conversation.
The audit occasionally flags something legitimate. The allow-list of amount-bearing tables has to be extended when a genuine policy or decision table needs one. That is a deliberate friction point — extending it is a decision someone makes explicitly rather than a column someone adds in passing.