A content-moderation service where K3 is the whole backend and one action-routed Go compile-mode function is the whole API:
- A deterministic rule engine runs first — banned/flagged term lists, PII (email/phone/card) and link-spam heuristics, plus custom rules — for a zero-latency, zero-cost verdict on obvious cases.
- An Ignite model then refines the verdict (nuanced categories, borderline calls); the stricter of the two wins, and if the model is unconfigured or down the service degrades to rules only with no code change.
- Submissions + their verdicts, the append-only audit log, and custom
rules live in the K3 SQL warehouse; full bodies are objects; a
vector collection finds similar past cases (
similar).
Public/private split: the moderate/check endpoint a product calls before publishing UGC is anonymous; the review queue + rule/key management need an admin key.
website / app ─POST─► moderate ─► ┌── rule engine (fast, deterministic) ──┐
│ merge = stricter wins ├─► decision
└── Ignite model (nuanced, optional) ─────┘ │
store in K3 (SQL·objects·vector)
The moderation core is CPU-bound string/regex work with a clear domain — a good
fit for Go. The design keeps that core (internal/moderation) free of the Ignite
SDK, the K3 client, and net/http, behind a small typed Repository interface,
so the entire product is unit-tested offline against an in-memory fake — no SQL
interpreter, no mocks of HTTP. The K3 / Ignite-Models concretes live in
internal/platform and are wired in by main.go.
| Tier | Action | What it does |
|---|---|---|
| PUBLIC (anon-safe) | moderate |
classify text and store it with its verdict (auto allow/reject, or queue on flag) |
check |
classify only — a dry run, nothing stored | |
public_overview |
counts by status (never leaks content) | |
| PRIVATE (admin key) | queue |
list items by status (default pending) |
get |
one item (full text) | |
decide / approve / reject |
set an item's status + append to the audit log | |
stats |
counts by status + active custom-rule count | |
audit |
the decision log | |
similar |
vector search over past content for near-duplicates | |
list_rules / add_rule / remove_rule |
manage custom block/flag terms | |
export |
dump content + verdicts as json / csv |
|
create_key / list_keys / revoke_key |
API-key (user) management |
The gate (internal/moderation/gate.go): PUBLIC actions are anon-safe (optionally
gated by a non-secret project key); PRIVATE need an admin key; keys ride in
the JSON body. A tier with no key configured stays open — set ADMIN_KEYS /
PUBLIC_KEYS (env) or mint keys at runtime to lock it.
main.go entrypoint — wires platform → domain, ignite.Start
info.yaml runtime: go
internal/moderation/ the product (SDK-free, offline-tested). Go's take on
the actions/ layout: dispatch core + one file per domain
server.go Server + routes + Handle pipeline.go moderate / check / overview
console.go queue/decide/stats/audit/… rules_actions.go custom-rule management
rules.go the rule engine gate.go public/private + key mgmt
types.go Repository + domain types helpers.go small helpers
server_test.go full offline smoke (go test)
internal/platform/ the concretes (net/http):
auth.go service-account → bearer token
k3.go K3 repository impl (SQL · objects · vector)
models.go Ignite-Models classifier (JSON verdict, graceful fallback)
sdk/ignite/ stdlib-only copy of the Ignite Go SDK, so the module
builds/tests offline (see go.mod `replace`)
web/ the console: server.mjs (proxy + /inbound) + index.html + app.js + Dockerfile
tests/ui_smoke.mjs offline UI/proxy/webhook test (mock backend)
go test ./... # backend: rule engine, model escalation, moderate→queue→
# decide→audit, gate, custom rules, export — all offline
go vet ./... && gofmt -l . # static checks
node tests/ui_smoke.mjs # UI: static host, /api proxy + key injection, /inbound webhookEverything builds and tests with the standard library alone — the vendored
sdk/ignite stub means no module downloads. You can also drive the real binary:
PORT=8080 go run . # then, in another shell:
curl -s localhost:8080/healthz # → ok
curl -s -XPOST localhost:8080/ -d '{"action":"check","text":"i will kill you"}'
# → {"ok":true,...,"decision":{"action":"block","categories":["violence"],...}}check needs no credentials (rules-only); moderate and the admin actions need a
service account so K3 is reachable.
export DODIL_SA_ID=... DODIL_SA_SECRET=...
# The backend is a Go compile-mode function — deployed from the repo root, no Dockerfile.
dodil ignite app deploy content-moderation --code . --allow-unauthenticated --tier small \
--env DODIL_SA_ID=$DODIL_SA_ID --env DODIL_SA_SECRET=$DODIL_SA_SECRET
# lock the private tier: --env ADMIN_KEYS=ak_your_admin_key
# The console is a BYOI image (Node static host + proxy).
dodil ignite app deploy content-moderation-ui --code ./web --dockerfile-path Dockerfile \
--allow-unauthenticated --tier small \
--env BACKEND_URL=https://content-moderation-<org>.ignite.dodil.cloud/The Ignite Go builder supplies the full SDK at deploy time; the in-repo
sdk/ignitestub (stdlib-only) is just sogo build/go testwork offline.