Skip to content
← Back to the profile

Applied AI

An agent inside the ERP, without handing it the keys

Two AI features in two products, built deliberately opposite because their threat models are opposite.

Period
2025 —
Role
Design and implementation
Stack
Laravel · PostgreSQL · Gemini · Ollama · OpenRouter · RBAC
Proof
almazenapp.djasoft.net.pe ↗

01 Context

AlmaZen is a multi-tenant ERP sold by subscription: inventory, purchasing, sales, POS and SUNAT electronic invoicing. Owners keep asking questions the database already answers — how much stock is left, what margin the month left, who owes money — but that take five screens to reach.

02 Problem

A conversational assistant over an ERP is an attack surface before it is a feature. If the model can query, it can query what the person asking has no right to see; and if it can write SQL, it can walk out of its own tenant.

03 Constraints

  • Multi-tenant: each company has its own schema and can never see another one.
  • Permissions already exist and are the source of truth. The AI does not get its own judgement.
  • The provider API key must never reach the client.
  • Cost has to be capped per subscription plan, not discovered at the end of the month.

04 Decisions

01

Tools, not a free prompt

29 read-only tools with an explicit contract each: stock levels, sales summaries, margin analysis, expiring batches, customer debt, cash position, inventory valuation. The model composes answers out of them; it does not invent access.

DiscardedGiving it the database and trusting the system prompt to hold the line. A prompt is a suggestion, not a permission system.

02

The permission decides which tools exist, not what the model replies

A user who cannot see margins is not handed the margin tool. The question then has nowhere to resolve — there is nothing to refuse, because there is nothing to call.

DiscardedFiltering the answer afterwards. That is asking a language model to keep a secret it has already been told, which is the one thing it is worst at.

03

Re-validate at execution time

The toolset is assembled when the session opens, and permissions can change inside a session. Every call checks again before it runs.

04

Fence the SQL tool three ways

Read-only SELECT, restricted to the caller’s tenant schema, with a forced row limit. Any one of the three alone would not be enough.

05

Provider behind a contract

A single interface the tools never see through. Gemini today; swapping it does not touch a single tool. Usage is metered as a monthly per-plan quota on the server, and the key stays in the backend.

06

The other product got the opposite design, on purpose

Master Color runs a public sales chatbot with no tools at all: the catalog is composed into the system prompt and the model can see nothing else. For an unauthenticated endpoint, no tool surface means no tool surface to abuse. Hardened with per-IP rate limiting, bounded message and history size and a capped conversation window, on a self-hosted Ollama model with OpenRouter as fallback.

How it works
  1. Questionfrom an authenticated user
  2. RBACdecides which tools exist
  3. Toolsetonly the permitted ones
  4. Modelhas nothing else to call

05 Result

  • Two products, the same technology, opposite threat models — and the design follows the threat model, not the fashion.
  • Authenticated and sensitive: tools plus a permission per user.
  • Anonymous and public: context only, and nothing to reach for.

06 Proof

Everything above is visible here: almazenapp.djasoft.net.pe ↗

← Back to the profile