Home / Stories / Cross-domain context
Story · Investigation Roadmap

Sixteen alerts, one switch. A failing playbook, one missing probe.

Incidents do not respect team boundaries. The cause of a Kubernetes alert can be a network uplink. The cause of a failed Ansible run can be a Vault (secrets store) pod three layers down that nobody noticed was sealed (locked). This story shows where the assistant is going: it will join what it knows across inventory, network, CI, cluster and cloud. Then you get one root-cause finding instead of a pile of symptoms. Most of it is designed but not yet built. Each step says which.

Starts fromA burst of alerts, or a failing CI job, that looks like many unrelated problems.
Looks atNetBox inventory (a database of devices and networks), with sites, devices, switch ports, VLANs and owners. Zabbix (a monitoring tool) host data, alert labels, CI job logs, the Git repositories that declare each dependency, live cluster state.
You getOne root-cause issue in place of N alert issues, or a draft merge request (MR) on the repository that owns the cause. The MR cites the symptoms elsewhere as evidence.
01 · Scenario A: the burst

Sixteen alerts in four minutes, across four teams.

orders-api is timing out, payments reports its database unreachable, a node exporter goes quiet, a queue backs up. Each alert, read on its own, points at a different service and a different owner.

Today each one gets its own investigation and, if it warrants one, its own issue. Sixteen honest findings about the wrong thing.

02 · What is built so far In development

A known-cascade table for Kubernetes alerts.

A rule-based correlator of co-firing alerts knows that KubeletDown explains the pod alerts on that node. It also knows that a scraper going dark makes its targets look down. It elects a root and annotates the others as dependents. It stays within one tenant, so it never mislabels an outage elsewhere as "fix the root".

It ships switched off and only annotates. It does not yet collapse N issues into one. It also cannot see what is outside the cluster: a switch, an uplink or a VLAN.

03 · Ask the inventory Roadmap

NetBox knows which switch port every host hangs off.

The assistant will join the instance or node label of each alert to a NetBox record, inside the same process. The join goes from address to host, host to interface, and interface to switch port and VLAN. Fourteen of the sixteen alerts land on hosts behind the same access switch uplink.

The join is strict. A key that matches two hosts abstains instead of guessing. The assistant says "not in NetBox" only after a complete read.

04 · A second witness Roadmap

Zabbix saw the port go down first.

For VM and bare-metal infrastructure that Zabbix monitors, the assistant will treat Zabbix problems as one more alert source. It will also use the Zabbix host inventory (OS, hardware, resources) as observed context beside NetBox.

Here Zabbix raised an interface-down problem on the switch uplink about ninety seconds before the first Kubernetes alert. Two independent sources, one cause.

05 · Masked at the source Roadmap

The model learns what a host is, never which host.

Inventory is core-infrastructure detail. The assistant will use addresses, MAC addresses, serials, VLAN ids, racks and site names for the join, then drop them. What reaches an AI model (LLM) is a closed-schema card. The card holds the role, platform family, status, update cadence, whether an owner is declared and an opaque team handle.

The assistant will express topology as relations between opaque handles, not as invented addresses. By default, it will drop any field that is not on the allowlist. A canary test will prove that no raw value survives.

06 · One finding Roadmap

One root-cause issue instead of sixteen.

The assistant will fold the dependents into a single issue. The issue names the shared uplink, lists every alert it explains, and states what could not be joined. The two alerts that did not fit the pattern keep their own investigation.

07 · Scenario B: the playbook

An Ansible run fails fetching a secret. Every hour.

The scheduled Ansible apply mints its SSH certificate from Vault before any play runs. Since last night it fails at that step: HTTP 503, Vault is sealed. The playbook repository is fine. The problem is somewhere else.

08 · Follow the declared edge Roadmap

From the CI log to the repo that owns the cause.

Rules, not an AI model, will match the failure signature (vault … 503 … sealed). The CI script itself names the Vault address and auth mount, so the dependency is declared, not guessed. The cluster map will resolve that Vault to its HelmRelease in the cluster repository.

In the cluster, the Vault pods are Running, 0/1 ready, zero restarts. The one auto-unseal attempt at startup failed against Google Cloud KMS. Nothing ever tried again.

09 · The missing check In development

Auto-unseal, and no liveness probe on seal status.

A rule in the availability pack looks for exactly this shape: a Vault HelmRelease with KMS auto-unseal and no liveness probe. KMS auto-unseal means a cloud key service unlocks Vault at startup. A liveness probe is a check that restarts a stuck pod. Readiness only takes a sealed pod out of the Service. A liveness probe on /v1/sys/health restarts the pod, and the restart retries the unseal.

The rule is meant to fire on its own, outage or not. The planned attribution in step 8 is what lets the MR cite the playbook failures as evidence.

orders-api p99 > 2s
shop · 14:02
PostgresUnreachable
payments · 14:02
NodeExporterDown ×3
platform · 14:03
QueueDepthHigh
shop · 14:03
KubeletDown ×2
platform · 14:03
BackupJobLate
platform · 14:04
TargetDown ×5
monitoring · 14:04
CertExpirySoon
shop · 14:05
16 alerts · 4 teams · 16 separate investigations
co-firing correlation · annotate mode · one tenant
  • KubeletDown elected root · node-7
  • pod alerts on node-7 dependent of KubeletDown
  • TargetDown ×5 blind pipeline artefact
  • PostgresUnreachable no known edge
  • orders-api p99 no known edge
  • NodeExporterDown ×3 no known edge
the cascade table stops at the cluster boundary: no switch, no uplink
inventory join · in-process only
host-a1 · node-7
host-a2 · node-8
host-a3 · db primary
host-a4 · queue
+ 10 more alerts
2 alerts · no match
switch-h3 · uplink 1access switch · VLAN handle v-2
14 of 16 alerts behind it
host ← interface ← switch port ← VLAN · ambiguous keys abstain
timeline · two independent sources
  • 14:00:31 Zabbix · interface down · switch-h3 uplink 1
  • 14:02:04 Alertmanager · orders-api p99
  • 14:02:10 Alertmanager · PostgresUnreachable
  • 14:03:00 Alertmanager · KubeletDown ×2
  • 14:04:12 Alertmanager · TargetDown ×5
Zabbix problem · Interface down
first signal · 93 s before the first Kubernetes alert
raw record · never leaves the process
name: db-07.dc1.example
primary_ip: 192.0.2.47
interface: eth0 · aa:bb:cc:…
switch_port: sw-dc1-r4 Gi1/0/17
vlan: 112
serial: XK4…
owner: (a person)
comments: free text
what the model sees
host: h-5c1e
role: database
platform: vm
status: active
update_cadence: monthly
owner_declared: true
owner_team: team-91a0
address_scope: private
behind: sw-3f02 (uplink u-1)
unmapped field → dropped · canary in every leaf → zero canaries out
16 alerts4 teams
Inventory join14 on one uplink
1 issue+ 2 kept separate
Issue · Access switch uplink down explains 14 alerts
category:reliabilitysource:alertrisk:high
CI · ansible apply · scheduled · job log
$ ci/mint-ssh-cert
  requesting fleet SSH certificate from Vault
  vault PUT auth/fleet/login: HTTP 503 ['Vault is sealed']
✕ job failed before ansible-playbook started

scheduled applies, newest first
✕ ✕ ✕ ✕ ✕ ✕ ✕ ✕ ✕ ✕ ✕ ✕ … 12 in a row
reconciler run-health: RED · 12 consecutive scheduled failures
Playbook repo · CIfailure signature: vault 503 sealed
Declared edgeCI script names the Vault address + auth mount
Cluster repo · Vault HelmReleaseresolved through the cluster map
Google Cloud KMSauto-unseal, tried once at startup
live: vault-0/1/2 · Running · 0/1 ready · 0 restarts · sealed
infrastructure/vault/helmrelease.yaml
spec:
  chart: { spec: { chart: vault } }
  values:
    server:
      ha:
        enabled: true
        config: |
          seal "gcpckms" {
            key_ring = "…"  crypto_key = "vault-unseal"
          }
      # no livenessProbe
  • KMS auto-unseal in the active config fires
  • Shamir / manual unseal never fires: a restart would re-seal it

The merge request you would get.

Illustrative, and on the roadmap as a whole. The probe values and the reasoning are the ones we use to build the rule. The cross-repository evidence section is the planned attribution step.

Draft

Enable the sealed-restart liveness probe on auto-unseal Vault HelmRelease vault/vault

from gitops-ai/vault-autounseal-liveness into main · 1 file · +7 −0
category:reliabilitysource:availability_rulesrisk:mediumxrev::LGTM
OverviewCross-repo evidenceValidationChanges
Summary

This Vault unseals itself through Google Cloud KMS. Vault attempts auto-unseal once, at startup. If that attempt fails, the pod stays sealed and Running indefinitely. Readiness only removes it from the Service. Nothing restarts it.

This MR enables the chart's liveness probe on /v1/sys/health?standbyok=true. A sealed pod fails the probe. The cluster restarts it, and the restart retries the unseal.

Why these values
  • initialDelaySeconds: 60, then failureThreshold: 6 × periodSeconds: 10 with a 3 s timeout. So a KMS blip of up to a minute does not cause a restart loop.
  • standbyok=true keeps healthy standbys at 200. It does not mask 474 (a standby that lost its active node), so that case restarts too.
When not to merge

If this Vault is uninitialised (501) or runs as an Enterprise DR / performance standby (472 / 473), this probe would restart-loop it. Check that neither applies before merging.

Symptom observed in another repository

The scheduled Ansible apply in the playbook repository has failed 12 times in a row at the certificate step with HTTP 503 ['Vault is sealed']. The CI script declares this Vault's address and auth mount, so the assistant attributes the failures here, to the repository that owns the Vault.

EvidenceSource
12 consecutive failed scheduled applies, same signatureplaybook repository · CI job logs
Vault address + auth mount named in the CI scriptplaybook repository · declared edge
3 pods Running, 0/1 ready, 0 restarts, sealedcluster · read-only
KMS auto-unseal, no liveness probecluster repository · this HelmRelease
Note for the playbook repository

A comment on the latest failed run links this MR. The assistant proposes no change there, because the playbook is correct. It was only the first thing to notice.

Checked before this MR was opened
  • The seal stanza is in the active config block (HA), not a commented-out or unused one.
  • No valuesFrom or post-renderer could override the probe.
  • The seal type is KMS-backed, so a restart retries the unseal instead of re-sealing a hand-unsealed pod.
After merge, expect
  • Vault pods roll one at a time. Each comes back unsealed and Ready.
  • vault status shows Sealed: false on all replicas.
  • The next scheduled Ansible apply passes the certificate step.
Rollback

Revert this MR. The probe is the only change.

     server:
       ha:
         enabled: true
         config: |
           …
+      livenessProbe:
+        enabled: true
+        path: "/v1/sys/health?standbyok=true"
+        initialDelaySeconds: 60
+        periodSeconds: 10
+        failureThreshold: 6
+        timeoutSeconds: 3
R
independent reviewer second opinion, no pipeline context

Confirmed the seal stanza is live config and KMS-backed, and that the probe path tolerates standbys. The restart-on-sealed behaviour is the intended fix. LGTM.

What keeps it honest.

Joining domains multiplies what can go wrong. These are the design rules, not afterthoughts.

Masked at the sourceInventory passes through a closed-schema projection before it can reach an AI model. The projection drops unknown fields by default and drops people's names entirely. Join keys never appear on the card. This is identifier masking with pseudonymous handles. We do not claim that the data cannot be linked back.
No guessing on joinsA key that matches two records abstains. An address and a hostname are only merged when one record carries both. "Absent" requires a complete read of a declared scope.
Declared edges onlyThe assistant attributes a CI failure to another repository only when the repository itself declares the dependency. An address injected from the environment makes the answer UNKNOWN, never a guess.
The wrong-direction proposal is pinnedThe probe rule never fires on a Vault with Shamir or manual unseal. There, a liveness restart would re-seal a pod someone unsealed by hand.
Stale inventory is visibleEach finding that leans on NetBox reports how old the record is. A stale NetBox produces visible doubt, not silent false drift.
Deterministic firstThe join, the failure signatures and the attribution are rules, not AI model calls. The AI model will see a masked card only after a measured A/B test shows that it helps.

Where this stands.

Live
  • Per-alert investigation with cross-cluster and ownership gates
  • Identifier masking on the review lane's LLM egress
  • Read-only cluster state for every investigation
In development
  • Co-firing correlation for known Kubernetes alert cascades (annotate mode, off by default)
  • Vault auto-unseal liveness rule in the availability pack
  • Scheduled CI run-health for an Ansible repository (report-only)
Roadmap
  • Read-only NetBox client and closed-schema host cards
  • Inventory join for alerts: shadow first, then inject after an A/B
  • Zabbix as an alert source and host-inventory source
  • CI-symptom to cross-repo root-cause attribution
  • NetBox-versus-git drift as draft MRs
← Previous storyAnsible CI/CD