Step-by-step: stand up Civo Project Mercury on the Local Proxy so AI prompts and the LLM key never transit the US-parent Base44 backend.
Client-provisioned infrastructure
Civo "real AI" and Project Mercury are provisioned by the client, not by MapFlow. MapFlow does not supply the Civo account, the LON1 GPU instance, the inference API key, or the Project Mercury infrastructure. The client stands up the sovereign inference host in their own Civo account (London region); MapFlow's Local Proxy only shuttles encrypted prompts to the client's endpoint and receives the textual answer back. TheMERCURY_API_KEY,MERCURY_API_BASE, and the account-side zero-retention / fine-tuning-opt-out flags below are all set in the client's Civo dashboard. Treat this guide as the hand-off spec the client's Civo admin follows.
Zero-exposure inference — no external training
All AI-assisted metadata and schema analysis operations route through localized, isolated execution loops on the client-provisioned sovereign host. Plaintext transaction payloads are cryptographically blinded at the network boundary, ensuring that no raw customer data or PII is transmitted to, or utilised for the continuous training of, external large language models. The US-parent backend only ever sees encrypted prompt material and receives only the textual answer back.
Source databases
SQL Server / Oracle / MS Access on the council estate, reached only via the Local Proxy on the operator's Windows machine. No inbound ports, no internet-facing API.
LLM inference
Ollama (Llama-3 / Phi-3 / Mistral / Qwen) bound to 127.0.0.1:11434, reached only via the proxy's /llm-invoke route. Benchmark-verified working. Zero internet egress during inference.
Postgres warehouse
Can run locally on the operator's machine or on a VM inside the corporate network (self-hosted Postgres / standalone Postgres). Point a MapFlow DBConnection at the corporate-network host. No cloud provider in the warehouse path.
MapFlow orchestration (external)
The Base44 backend — the platform — runs in the cloud and orchestrates jobs, mappings, auth, and scheduled tasks. It holds metadata only: project config, mapping definitions, job records, audit logs. It never holds the AES key or plaintext citizen data under the default local_proxy mode.
The one accepted external egress: the final load of mapped data into Salesforce, which uses the Salesforce REST API. Salesforce is the client's contracted destination — the hand-off is the documented, accepted boundary, not a residency gap in MapFlow's processing.
Net: the AES-256-GCM key, the plaintext citizen data, and the LLM prompts all stay inside the operator's estate. A US CLOUD Act production order against the Base44 platform returns ciphertext and non-PII metadata only — never plaintext, never LLM transcripts. Obtaining plaintext becomes a UK lawful-access matter against the operator, not a retrieval from a US provider.
1. Browser → Local Proxy. The app POSTs ciphertext_inputs (ENC:-prefixed), non_pii_metadata, and a prompt_template to http://localhost:3001/llm-invoke.
2. Proxy decrypts in RAM. The proxy uses the DPAPI-held AES key to decrypt ENC: values in volatile memory — never logged, never persisted.
3. Proxy → UK LLM. The proxy fills the template, then calls Civo Project Mercury (LON1) with an API key held in its own env — not in Base44 app secrets.
4. Result → Browser. Only the parsed result returns. Base44 (and any US CLOUD Act order) sees ciphertext + non-PII metadata + the result — never the plaintext prompt or the LLM key.
src/tools/postgres-proxy.py) already running on the operator's UK machine with the DPAPI-held AES session key loaded (the same key used for PII encryption).llm_invocation_mode and llm_provider.127.0.0.1:11434 if you want the local_ollama fallback.On the machine running the proxy, set three environment variables before starting it:
# Windows (PowerShell, current session) $env:MERCURY_API_KEY = "mw-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" $env:MERCURY_API_BASE = "https://inference.lon1.mercury.civo.com" $env:MERCURY_MODEL_ID = "mercury-edge-30b" # Then start the proxy (it picks the key up from env) python postgres-proxy.py --key-file C:\\keys\\aes-key.txt # Linux / macOS export MERCURY_API_KEY="mw-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" export MERCURY_API_BASE="https://inference.lon1.mercury.civo.com" export MERCURY_MODEL_ID="mercury-edge-30b" python3 postgres-proxy.py --key-file /etc/mapflow/aes-key.txt
LON1 pin enforced in code. The proxy's sovereign_llm_invoke refuses a MERCURY_API_BASE that looks generic (bare civo.com / civo.cloud). It must carry a regional/mercury host so the handshake never exits UK borders.
These satisfy ICO / UK GDPR guidelines for sovereign inference. Without them Mercury is "on Civo" but not sovereign.
civo_mercury.Local Proxy (recommended) — the default. This makes backend AI functions refuse to assemble prompts; the browser drives them via callLlmViaProxy.mercury-edge-30b (and mercury-frontier if you use the 256B variant).With llm_invocation_mode = local_proxy, the shared callLlm() throws if a backend function tries to invoke an LLM — so the plaintext prompt can never be assembled in Base44 memory. The frontend must use callLlmViaProxy instead.
The browser-side caller lives at src/lib/callLlmViaProxy.js. A feature passes ciphertext + non-PII metadata + a template; the proxy fills the template after decrypting:
import { callLlmViaProxy } from "@/lib/callLlmViaProxy";
// PII classification example — column value arrives as ENC: ciphertext
const result = await callLlmViaProxy({
proxyUrl: "http://localhost:3001",
promptTemplate:
"Classify the PII category of this column value. " +
"Column: {column_name} (type {column_type}). Value: {sample_value}. " +
"Return JSON {category, confidence}.",
ciphertextInputs: {
sample_value: rowSampleValue, // "ENC:..." — decrypted in proxy RAM
},
nonPiiMetadata: {
column_name: "national_insurance_number",
column_type: "varchar(12)",
},
responseJsonSchema: {
type: "object",
properties: {
category: { type: "string" },
confidence: { type: "number" },
},
},
});
// result === { category: "government_id", confidence: 0.97 }Placeholders {column_name}, {sample_value} are substituted on the proxy after decryption — so the browser never holds the plaintext either.
curl http://localhost:3001/health — proxy up, key loaded, memory bounded.curl -X POST http://localhost:3001/llm-invoke \
-H "Content-Type: application/json" \
-d '{"prompt_template":"Reply with the single word PONG.","non_pii_metadata":{}}'llm_invocation_mode = local_proxy.callLlmViaProxy and confirm the proxy log shows [/llm-invoke] handling the call (no Base44 backend function is hit).If MERCURY_API_KEY / MERCURY_API_BASE are unset, sovereign_llm_invoke calls local_llm_generate at 127.0.0.1:11434 — zero network egress beyond the UK machine. Set the MapFlow provider to local_ollama to use this path deliberately.
This path runs fully air-gapped: the Ollama host never needs a route to the public internet during inference (model weights are pulled once on a separate jump host and transferred across). On a Windows operator machine, Ollama installs as a native service bound to loopback — see the Local Windows LLM Setup guide for the PowerShell procedure and proxy wiring. This is the strongest residency posture available: true LLM reasoning with zero internet egress and zero cloud dependency.