Architecture

Every boundary, and exactly what crosses it.

Odonat asks for write access to your production environment. That is only reasonable if you can see where the process runs, what it is permitted to call, and what stops it when it is wrong. Everything on this page is taken from the Helm chart you install, not written for the website.

Deployment topology

One namespace, two containers, zero inbound ports.

Everything Odonat installs lives inside the innermost box. Nothing outside your VPC can open a connection to it.

Boundary crossings

Six crossings. Two of them carry nothing.

This is the complete list. If a reviewer finds a seventh, that is a bug and we want to hear about it at contact@odonat.ai.

Public internet → agent
none
Nothing. There is no inbound path.
NetworkPolicy ingress: [] is an empty rule set, which denies all inbound at the CNI. The agent is never dialled; it polls outward.
Agent → Kubernetes API
outbound TCP 443
Reads pods, services, nodes, events, namespaces, pod logs, deployments, replicasets, statefulsets, daemonsets and metrics. Writes deployments/scale and statefulsets/scale, nothing else.
ClusterRole. The scale subresource cannot change an image, a command, an env var, or any other part of a pod spec.
Agent → your git host
outbound TCP 443
One pull request that reverts one commit, opened and never merged. Credentials in the request body are replaced with fixed markers: API keys, bearer tokens, Slack tokens, private key blocks, and passwords inside connection strings. There is no prompt and no foundation model: correlation is deterministic Go, so nothing is sent to a model at any point.
Envoy sidecar in the same pod, configured failClosed. If the filter errors the stream is reset rather than forwarded, and the chart refuses to render with failClosed disabled.
Agent → GitHub / GitLab
outbound TCP 443
Reads repository contents and checks. Opens pull requests.
App scope. It cannot force push and it cannot alter branch protection.
Agent → your SIEM
outbound
A signed record of every action requested, as it happens.
An append-only log in your own cluster, and optionally a webhook you choose. The compliance record lives in a system you control, so it survives us.
Agent → disk
none
No proprietary source is ever written to disk.
Working state lives on a memory-backed volume; the root filesystem is mounted read-only.
Permission boundary

What the ClusterRole does not contain.

A permission list is easy to read charitably. The useful question is what is missing, because that is what a fully compromised agent still cannot do. These verbs appear nowhere in the chart.

create
It cannot bring new workloads into existence.
delete
It cannot remove a pod, a deployment, or a namespace.
exec
It cannot get a shell in any container, including your app containers.
secrets
It cannot read a Secret. Not scoped narrowly. Absent entirely.
escalate / bind
It cannot grant itself or anything else a wider role.
any non-scale write
The only mutating verbs it holds are patch and update, and only on deployments/scale and statefulsets/scale.

Pod hardening

runAsNonRoot: true
UID and GID 10001. Never root.
readOnlyRootFilesystem: true
Both containers. Writable paths are explicit mounts.
allowPrivilegeEscalation: false
No setuid path to more privilege than it started with.
capabilities.drop: ["ALL"]
No Linux capabilities retained.
seccompProfile: RuntimeDefault
Syscall filter applied at the pod level.

These are set in the Deployment template, not exposed as values. There is no override that turns them off.

Control path

Four stages, and what bounds each one.

Each stage degrades toward doing nothing. The interesting column is the third.

01
Pre-merge (roadmap, not built)0s

NOT BUILT. The intent: correlate a pull request against every workload that changed in the window, so latency regressions and malformed config surface before the merge button does anything. There is no traffic capture, no replay runner and no CI integration in the agent today. Every row below this one is live.

Bounded by Nothing to fail, because nothing calls it. Odonat polls Alertmanager beside production and is never invoked by CI. A reliability tool that can block your deploys is a new source of outages, which is why this stage stays out of the merge path even once it exists.

02
Rollout<10s to halt

NOT BUILT. The intent was a canary: 1% of production traffic observed for six minutes. Argo Rollouts and Flagger already do progressive delivery, so the useful work is reading their analysis results rather than reimplementing traffic splitting.

Bounded by Traffic is shaped through your existing Istio or App Mesh, not replaced by ours. If the agent dies mid-rollout your orchestrator carries on.

03
Production anomaly<30s to a revert PR

Alerts normalise into one shape. The causal engine matches them against workloads that changed in the window, eliminates any that shipped after the symptom began, and returns a culprit with a confidence score.

Bounded by There is no model anywhere in this path. Every stage is deterministic code, so the same inputs produce the same verdict and there is no provider that can be down, rate-limit you, or change its behaviour under you.

04
Action60s hard timeout

Above 85% confidence the executor opens a revert pull request, the single entry on a compiled allowlist. Below it, no action is taken and the decision is recorded with the reason. There is no approval queue: a refusal is a refusal, and the record is how you see it.

Bounded by The allowlist is compiled into the binary, not prompted. Drops, deletes, truncates, namespace deletions and force pushes raise an exception the calling code cannot catch. The ServiceAccount holds no mutating verb at all, enforced by CI, so the agent cannot change your cluster even if the gate were wrong.

The chart is plain YAML, short enough to read in one sitting, and your platform team should read it rather than take our word for any of the above. What it renders, and the values you actually override, are on the deployment page.