Governance · read-only by construction
Policy is code.
Not a prompt.
SQAI treats the model as what it is: an untrusted client. Allow-lists are fixed in code when the tool is constructed, checked in-process before anything executes, and absent from the model's protocol. There is nothing to talk the system out of.
IIThe narrowing, in full
Three layers. Each may only remove.
Every answer passes the same containment. A hash-pinned contract defines what exists. Your policy narrows it to what is allowed. One typed request must fit inside both.
- 01
The contract
4,778 capabilities in the pinned runtime; 4,574 exposed to agents, every one read-only. The other 204 are excluded outright — for a model, they do not exist.
- 02
Your policy
Three allow-lists — allowedSources, allowedFields per source, allowedFunctions — fixed at createSQAI() time. Functions default to "all-readonly".
- 03
One request
A typed plan, validated against the contract, then checked against policy in-process — before execution, never after.
allowedSourcesordersallowedFieldsregionrevenueunit_priceallowedFunctionsstats.medianfinance.npvQuerySpec v1 · median(revenue) by region · policy ✓
contract_hash sha256:79f1c5a6c7164e7e9e1750e70a5c03292fa87eb52d8148a740c06695924be9a1
Narrow only.
A policy can shrink the surface; nothing can grow it. Name a capability the contract does not expose and createSQAI() throws unsupported_operation at construction — the app fails before an agent ever connects.
And the model's tool input carries no allowed* field. The policy is not in the protocol, so there is nothing for a prompt — or an injection — to widen.
IIIThe policy, in code
Written once. Checked every time.
Most governance tooling sits beside the data path and observes it. SQAI's policy sits inside the path: three allow-lists in the constructor, enforced in-process before every execution.
const sqai = createSQAI({
sources: { orders },
// policy — fixed here, invisible to the model
allowedSources: ['orders'],
allowedFields: {
orders: ['region', 'revenue', 'unit_price'],
},
// functions default: 'all-readonly'
allowedFunctions: [
'stats.median', 'finance.npv',
],
});- allowedSources
- The registered sources a plan may name. Anything else returns policy_denied_source.
- allowedFields
- Columns, per source. A plan that touches any other column returns policy_denied_field.
- allowedFunctions
- The read-only functions a plan may call — default "all-readonly". Anything outside returns policy_denied_function.
- tool input
- Carries no allowed* field. The model never sees the policy, so it can never send a wider one.
The denial ledger
Denials are data, not exceptions. Each carries a stable code an agent can read — and none is retryable, so a loop cannot wear the policy down.
at construction
unsupported_operationThe policy names a capability the contract does not expose. Construction throws; the app fails before any agent connects.
at request time
policy_denied_sourceThe plan names a source outside allowedSources.
policy_denied_fieldThe plan touches a column outside that source's allowedFields.
policy_denied_functionThe plan calls a function outside allowedFunctions.
source: "sqai" — every policy denial arrives as a structured, non-retryable result. The tools never throw.
IVCustody
Results are held, not kept.
A full result set never lands in the model's context. It is parked in a governed store and referenced by id; the model receives a declared sample.
The result store
Governed store · TTL 15 min
- result_id
- 16 random bytes, base64url. Unguessable, never sequential.
- authorization
- Tenant-bound. A wrong tenant reads result_not_found — the store never confirms existence.
- ttl
- 15 minutes. Then the entry is gone.
- capacity
- At most 256 results — 64 MB total, 16 MB per tenant.
The store holds the result. The model reads a sample and says so.
Model-context caps
defaultLimit100row limit applied when a plan does not set onemaxExecutionRows1,000hard ceiling on rows any execution returnsmaxRowsToModel25the most rows a model context ever receivesmaxCellsToModel250the most cells a model context ever receivesmaxBytesToModel32,000the most bytes of result data a model ever receivesTruncation is always declared in the result. Partial rows are never shown — a row arrives whole or not at all.
The audit angle
17%of organizations can reconstruct an agent's full tool-call sequence after the fact
GDPR Article 30 and SOC 2 CC7.2 expect exactly those records.
Every SQAI answer already carries its own reconstruction:
plan_hash · invocation_hash · computation_hash · contract_hash
Replay is a lookup, not an investigation. How replay works →
VIQuestions
Governance, asked directly
Can the model widen its own access?
No. Policy is fixed at createSQAI() time and checked in-process before execution. The model's tool input carries no allowed* field, so there is nothing in the protocol to widen — a request outside policy returns a structured policy_denied result.
Is read-only a flag that something could bypass?
It is not a flag. The exposed contract holds 4,574 read-only capabilities; a write path is absent by construction, not disabled. Prompt injection can change what a model asks for — it cannot add capabilities to a pinned contract.
What records exist for a GDPR Article 30 or SOC 2 CC7.2 review?
Every result carries plan_hash, invocation_hash, computation_hash and contract_hash. The exact computation can be replayed and verified against those hashes long after the fact.
Where does result data live after a query?
In a tenant-authorized result store: ids are 16 random bytes, entries expire after 15 minutes, and the store is capped at 64 MB total and 16 MB per tenant. The model context receives at most 25 rows, 250 cells and 32,000 bytes — truncation always declared.
VIINext
Govern the answer.
Bring a schema and a compliance question. We will show you the policy that answers it.