Concepts

Domain model

Tiyi's domain is small on purpose. Seven first-class resources cover every operator action, every CLI command, and every UI page. Once these click, the rest of the product is obvious.

The seven resources

Site
An HTTP host Tiyi proxies. Carries TLS settings, an upstream pool reference, and an attached WAF policy. The smallest unit you enable or disable.
Upstream pool
A set of backends behind a site. Round-robin or weighted load balancing, SNI-aware health checks, and reuse across multiple sites.
Certificate
A TLS keypair. Either uploaded (self-signed, CA-bought, enterprise-signed) or ACME-issued (HTTP-01 or DNS-01). Encrypted at rest with the KEK.
WAF policy
The compiled artifact Coraza enforces per request. CRS Core layer + HTTP policy + limits + rule tuning + IP lists + custom rules + plugins + rate limits + exclusions.
Agent
A node running the data plane (Caddy + Coraza). Receives signed config bundles from the primary over a long-lived gRPC stream.
Telemetry rollup
Counters and Top-K samples produced by the open-bucket pipeline. Drives the dashboard, the Top-K explorer, and the Prometheus exporter.
Audit row
One hash-chained record per mutation, attributed to the JWT subject. The compliance surface — every change has a tamper-evident receipt.

Sites and upstream pools

A site binds a hostname to TLS settings, an upstream pool, and a WAF policy. The applier translates each enabled site into a Caddy server block at apply time — Caddy JSON is a compile target, not a storage format.

Upstream pools are detached from sites by default. One pool can back many sites; deleting a pool that's in use is rejected with UPSTREAM_IN_USE and a list of referencing sites. Sites can also use an inline upstream URL for ad-hoc cases that don't need pool sharing.

Access log recording mode

Each site has a recording_mode field that controls access-log volume:

Security events (Coraza rule hits) are always written regardless of access-log mode.

Certificates

TLS material is a first-class resource — never inline on the site. Tiyi supports four issuance paths, all encrypted at rest with a 32-byte KEK loaded from crypto.kek_file:

The dashboard exposes certs_expiring_14d / certs_expiring_30d / acme_renewals_failed_24h / acme_orders_pending as KPIs. Two default alerts are seeded on first boot: Certificates Expiring Soon and ACME Renewal Failures.

WAF policy

A policy is a structured domain object that compiles to Coraza SecLang at apply time. The compiler is deterministic — same policy in, same SecLang out. The 12 tunable layers (in the order they apply):

  1. CRS Core — paranoia level, blocking and executing PL, inbound/outbound thresholds, sampling.
  2. HTTP Policy — methods, versions, content types, charsets, restricted extensions/headers, method override.
  3. Limits — six numeric fields: max request body, max response body, etc., with CRS-default placeholders.
  4. Rule tuning — per-rule overrides: DEFAULT, DISABLE, LOG_ONLY, SCORE_OVERRIDE, optionally scoped to a single site.
  5. IP lists — allow / deny / monitor lists. Entries can be CIDR or geo:CC pseudo-entries (alpha-2/alpha-3 country codes plus PRIVATE / LOOPBACK).
  6. Custom rules — operator-authored SecLang or visual-spec rules in the 8000000–8999999 ID range.
  7. Plugins — CRS rule-exclusion packages (WordPress, Drupal, Nextcloud, phpBB, phpMyAdmin, XenForo, cPanel, DokuWiki). Offline archive upload supported.
  8. Rate limits — per-endpoint and client-scope buckets with block or log actions.
  9. Exclusions — phase-1 tx.crs_exclusions_<slug> opt-ins, optionally scoped with @beginsWith.
  10. Preview — read-only SecLang dump for the current revision.
  11. Versions — snapshot per save, side-by-side diff between any two revisions.
  12. Test lab — fire fixture requests through the compiled bundle without touching the live data plane.

Per-site overrides overlay scalar fields (paranoia, thresholds, sampling) on top of an attached policy without forking it.

Agents

Every node that proxies traffic runs an embedded agent — including the primary and the warm secondary. Agents hold a long-lived ConnectRPC bidi stream to the primary. The reconnect loop tries the primary first, then the secondary, then retries with exponential backoff. The secondary accepts read-only agent streams.

Updates flow as ConfigUpdate{revision, bundle, signature} messages. The agent verifies the bundle signature against the pinned ed25519 public key (TOFU on first contact, refuses to re-pin afterwards), checks that the revision is strictly greater than the last applied, applies it to the local Caddy, and reports the result. Replay and reorder are free — updates are idempotent by revision.

Why uniform agents? The primary's embedded agent is not special. It connects to its own API address using the same enrollment and streaming protocol as any remote agent. The data plane is therefore tested by the same code path everywhere — no "primary is different" caveats.

Telemetry

Telemetry runs as a parallel pipeline that ends per-event SQL writes. The path:

  1. Sharded ingress ring — log-forwarder events fan into per-shard rings keyed by site.
  2. 10-second open-bucket aggregator — per-shard worker accumulates counters into a 10-second bucket.
  3. Misra-Gries Top-K — bounded summary for high-cardinality dimensions (client IP, path, UA, ASN, attack tag) with an __other__ bucket so SUM(*) equals true traffic.
  4. Per-day SQLite partitions at logs/rollup_10s/YYYY-MM-DD.db; downsamplers fold these into rollup_hourrollup_dayrollup_month idempotently.
  5. Sample rings — per-IP LRU + global rings with an append-only circular-file WAL for replay-on-boot.
  6. API tree — per-site URL prefix tree with bounded node and depth guards; idle leaves evict to api_tree_archive.
  7. Reads — REST API at /api/v1/telemetry/* and a Prometheus exporter at /metrics on the local admin socket.

Audit chain

Every mutation (site, upstream, cert, policy, IP-list binding, rate-limit endpoint, trust-profile change, custom rule, alert rule, etc.) appends one row to audit_event. Each row stores:

The Vben Admin Verify chain button walks the chain and confirms each link. SIEM egress with siem.filter.include_audit forwards every audit row to your Splunk / Elastic / Sentinel pipeline.


That's the whole model. Every CLI command, every UI page, every RPC operates on one of these seven resources. The rest of these docs reference them by name without re-defining.