Skip to content

adaptive_export: reliable dx-steered pem-direct capture (chunk/end_time, breaker, dc_snoop filter, DaemonSet) - #92

Open
ConstanzeTU wants to merge 100 commits into
mainfrom
fix/ae-protocol-export-pxexport
Open

adaptive_export: reliable dx-steered pem-direct capture (chunk/end_time, breaker, dc_snoop filter, DaemonSet)#92
ConstanzeTU wants to merge 100 commits into
mainfrom
fix/ae-protocol-export-pxexport

Conversation

@ConstanzeTU

Copy link
Copy Markdown

Stacked on #89 (dark-vector tables). Makes the dx-steered OrderExportAll/OrderQuery capture reliable on a single node-local PEM, and turns the AE bootstrap into a functional pem-direct DaemonSet. Validated e2e on a reproducible skaffold stack (soc-stack + bob redis-apps pixie-io#184 + this): kubescape → dx → AE, redis_events/dc_snoop/stack_trace/conn_stats/dns_events captured, deduped via ReplacingMergeTree.

Commits (each independent, tested):

  • chunk + end_time boundQueryFor bounds the source scan on both sides; OrderQuery walks the window in sub-windows, captureSpan subdivides only on timeout. Default is one query/table (OrderChunk=600s) — pre-chunking every table 10x-amplified queries on the one PEM.
  • dc_snoop infra filter — the dx-steered path now drops own-stack/system comms + infra namespaces (was cron-preset only). Adds a px.logicalNot(px.contains(...)) substring drop for kernel threads (kworker/…) that exact-match misses; workload comms (redis-*) kept.
  • subdivision circuit-breaker + depth cap — sustained transient failures stop subdivision so a saturated PEM isn't stormed.
  • /query window-widening — a near-zero (point) window is widened to the lookback, else it matches no rows.
  • bootstrap = pem-direct DaemonSet — the old replicas:0 Deployment never ran and couldn't do node-local pem-direct; replaced with the working config (EXPORT_MODE=never, control surface, MAX_INFLIGHT=4) + control Service.

RCA + numbers: biz/PoC/OTel/RCA_ae_capture_20260803.md (internal).

Known follow-up: node-scoped tables (dc_snoop, dx_*) are re-pulled once per steered pod on a node, so raw > FINAL when multiple pods on a node are steered (RMT still dedups). Fix = per-(node,window) dedup of node-scoped pulls in OrderExportAll.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 89c8dea3-a9de-460e-942c-40d53c5c2de7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from feat/ae-dark-tables-on-main to main August 7, 2026 18:26
entlein added 9 commits August 7, 2026 20:31
…e fix)

Root cause of the flaky dx-steered capture (dc_snoop/http erratically 0 while
light tables always land): OrderExportAll fans out ~20 tables concurrently, each
OrderQuery issued ONE unbounded PxL query over the full ~600s control window
against the single node-local PEM (pem-direct). QueryFor only set start_time, so
every query re-scanned [sliceStart, now] and post-filtered — the heavy tables
materialize huge result sets on a saturated PEM and lose the fixed 180s deadline
race, dropping out; the cheap tables (redis/conn/stack) return instantly and
survive. Reconcile fingerprint: the same dc_snoop query returns 2459 rows in
isolation but 0 + 1 err under the fan-out.

Fix (durable — removes the data-volume↔deadline coupling, not just tunes it):

- pxl.QueryFor: bound the PEM source scan on BOTH sides. Emit a relative
  end_time (floored toward now so nothing real is clipped; the exact upper bound
  stays enforced by the df.time_ < sliceEnd nanos post-filter) whenever sliceEnd
  is in the past. Live-edge slices keep scanning to now (no end_time), preserving
  prior behavior for the most-recent window.

- controller.OrderQuery: walk the capture window in OrderChunk-sized sub-windows
  (default 60s, env ADAPTIVE_ORDER_CHUNK_SEC), each a both-sides bounded query, so
  no single query re-materializes the whole window. captureSpan adaptively halves
  any chunk that still fails with a transient (deadline/overload) error down to
  orderMinChunk (1s); non-transient errors (missing dark table) surface
  immediately without wasteful splitting. Overlapping/retried spans dedupe in the
  ReplacingMergeTree evidence tables, so re-pulls are idempotent. One aggregated
  reconcile row per table (not per chunk).

Chunks run sequentially per table, so OrderExportAll's per-table concurrency is
unchanged while each table now issues cheap bounded queries instead of one
firehose — reliable capture without needing the global inflight throttle set.

Tests: queryfor end_time present for past windows / absent at the live edge;
OrderQuery chunking, single aggregated reconcile row, adaptive subdivision on
transient error, no-split on non-transient error, termination at min-chunk.
… (dc_snoop)

The dx-steered OrderExportAll path applied only a partial comm denylist and NO
namespace filter to the node-scoped dark-vector tables — unlike the shipped cron
preset (script/presets dc_snoop.pxl __DC_SNOOP_EXCLUSION__, built from presets.go
defaultExcludeNamespaces + defaultExcludeComms). So every dc_snoop capture drowned
in infra dcache churn: on a real k3s node a single window returned ~54k rows
dominated by ConfigReloader/iptables/CNI(host-local,bridge,flannel,loopback)/host
daemons(systemd-udevd,dbus-daemon,tailscaled)/kubevuln — burying the salient attack
specimens (whoami/cat/getent reading /etc/shadow + the SA token).

- Extend darkExcludeCommsDefault with the host/CNI/node daemons that were leaking
  (systemd-udevd, host-local, bridge, flannel, loopback, bandwidth, dbus-daemon,
  mount, umount, tailscaled, grpc_health_pro, kubevuln, opm, kube-proxy, …).
- Add darkExcludeNamespacesDefault + darkNamespaceExclusion(), applied in the
  IsDarkVector branch AFTER PodEnrichPxL resolves df.namespace, dropping infra
  namespaces (pl, kube-system, clickhouse, …). Blank-namespace transient rows
  survive (each `!=` is true for ''), so the attack's short-lived children — which
  resolve blank — are never dropped. Overridable via DC_SNOOP_EXCLUDE_NAMESPACES.
  Kept in sync with script/presets.go.

Tests: infra namespaces + host/CNI comms dropped; df.namespace never pinned to the
alert pod (node-scoped); env override replaces the default list.
… depth cap)

Live RCA on aeprod54: the chunk fix is correct in isolation (pem unit suite —
dc_snoop 54k, redis/conn/stack written per-chunk) but UNSAFE under the dx steering
firehose. dx does generic collect-per-alert, so OrderExportAll (20 tables) fires on
every noisy pl system pod continuously; all land on the ONE node-local PEM
(pem-direct) → it saturates → 100% DeadlineExceeded. captureSpan then split every
timeout into two narrower retries, amplifying a busy PEM into a query storm where
nothing completes (observed: "0 ordered pixie rows written" across the whole run;
draining dx + restarting AE → pem-direct instantly serves again).

Make subdivision safe:
- Circuit-breaker: orderTimeoutStreak (atomic) counts CONSECUTIVE transient
  failures; any success resets it. Above orderBreakerTrip (8) captureSpan stops
  subdividing — a saturated PEM must not be flooded with retries. It still splits a
  genuinely-oversized window on a healthy PEM (the reset keeps that path live).
- Depth cap: maxOrderSplitDepth (3) bounds one chunk to ≤2^3 leaf queries even if
  it keeps timing out (was ~64 splitting 60s→1s).

Tests: a 10-chunk all-timeout window stays <60 queries (ungated ≈640); a single
transient failure still recovers (breaker resets on success, no latch).

NOTE (deployment, not code): the firehose root also needs dx steering scoped so it
doesn't fire 20-table captures on every noisy pl/system-pod alert — tracked
separately for dx-agent.
Live RCA (aeprod55): every dx-steered capture in the e2e returned 0 rows, and the
reconcile showed why — all 36 ordered captures had ~512ns-wide windows (width_s=0),
so they matched no pixie rows. /export/start already reaches back
controlExportLookback, but a control client that keys the /query window on a single
finding's event_time sends lo≈hi (a sub-microsecond span). That passes the lo<hi
validation yet captures nothing.

handleQuery now widens any window narrower than minControlQueryWindow (5s) to
controlExportLookback ending at hi — a point-in-time referral still captures the
evidence leading up to it. hi is preserved; comfortably-wide windows pass through
unchanged. Isolated /query probes (proper windows) already proved the capture path
works — dc_snoop 54k→16k filtered, redis/conn/stack per-chunk; this makes the
dx-driven path robust to degenerate windows too.

Tests: a 512ns window is widened to >=5s (hi preserved); a 120s window is untouched.

NOTE (dx-agent): dx should send a real window (or use /export/start) rather than a
point window per finding — tracked separately. This is the AE-side safety net.
The bootstrap manifest was a replicas:0 Deployment with minimal env (EXPORT_MODE=
auto, no pem-direct, no throttle) — it never ran and could not do node-local
pem-direct. Replace it with the working config that the e2e RCA validated:

- DaemonSet (one-per-node) so each pod queries its OWN node's vizier-pem at
  HOST_IP:50305 (pem-direct: node-local, desync-immune).
- dx-steered: EXPORT_MODE=never + CONTROL_ADDR=:9100 + the control Service
  (internalTrafficPolicy:Local so dx reaches its co-located AE).
- PEM-protection: ADAPTIVE_MAX_INFLIGHT_QUERIES_GLOBAL=4 and ADAPTIVE_ORDER_CHUNK_SEC
  =600 (one query per table, no window pre-chunking) so the AE never saturates the
  single node-local PEM it shares with dx. See RCA_ae_capture_20260803.

Secret still seeded per-cluster (unchanged).
…efault; trim comments

- queryfor.go: add darkExcludeCommSubstrings (kworker/ksoftirqd/rcu_/… — kernel
  threads with variable suffixes exact-match misses) applied via px.logicalNot(
  px.contains); add pause + systemd-logind exact. Workload comms (redis-*) untouched.
- controller.go: defaultOrderChunk 60s -> 600s (one query per table; pre-chunking
  10x-amplified queries on the single node-local PEM).
- Strip verbose comments across queryfor.go/controller.go/server.go + the AE manifest.

Test: kernel-thread substrings dropped, workload comms kept, pause dropped.
Deploys the dx-daemon DaemonSet + Service into honey and mirrors the
pl->honey secrets (jwt-signing-key, cluster-id, cloud-addr, api-key,
clickhouse http-url) via a before-hook, replacing the hand-applied
manifest used in the e2e. Deploy with:

  skaffold deploy -f k8s/vizier/dx/skaffold.yaml

CH http-url defaults to the soc clickhouse Service; override with
DX_CH_HTTP_URL.
Replaces the imperative seed-secret + patch-cloud-addr + sed-image +
kubectl-apply sequence with a single skaffold module:

  skaffold deploy -f k8s/vizier/adaptive_export/skaffold.yaml

- kustomize overlay reuses bootstrap/adaptive_export_{role,deployment}
  and pins the image via images: (ghcr aeprod tag) instead of sed.
- before-hook patches PL_CLOUD_ADDR :443 and seeds
  pl-adaptive-export-secrets ONLY when PIXIE_API_KEY/PX_API_KEY is set,
  never clobbering an existing secret with an empty key.
- LoadRestrictionsNone so the overlay can reuse the bootstrap manifests
  in place (no duplication/drift).

Pairs with the dx-daemon skaffold (k8s/vizier/dx). Bump the AE image by
editing newTag in kustomization.yaml.
…aths

The AE/dx skaffold configs lived inside their overlay dirs with kustomize
paths: [.], which skaffold resolves against the shell CWD (repo root), not
the config-file dir -> 'unable to find kustomization.yaml in /.../pixie'.

Match the repo convention instead (skaffold/skaffold_vizier.yaml et al.):
skaffold configs live in skaffold/ and reference overlays by repo-root-
relative kustomize paths. Overlays stay in k8s/vizier/{adaptive_export,dx}.

  skaffold deploy -f skaffold/skaffold_adaptive_export.yaml
  skaffold deploy -f skaffold/skaffold_dx.yaml   # run from repo root

- dx overlay gains a kustomization.yaml (was rawYaml).
- both validated with 'skaffold render' from repo root (image overrides +
  RBAC/DaemonSet/Service resolve).
@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — (reposting here, channel moved) please build the AE image from branch build/ae-ppid-plus-streaming-darkfix = #94 ppid/pcomm dc_snoop capture + the streaming-scanner dark-vector fix (PR #95, stacked on this #92).

Fixes the upid regression: the streaming TableScanner hardcoded px.upid_to_*(df.upid), so dc_snoop/creds_change/dx_* threw Column 'upid' not found and 0-exported on the steered path (confirmed live on 25d4077db). #95 wires pxl.PodEnrichPxL + node-scopes the dark tables. Reply with ghcr.io/k8sstormcenter/vizier-adaptive_export_image:<tag> when green — redeploying on rig 6a7be0ac. Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

Builds for 2e05ed29a (head of fix/ae-protocol-export-pxexport)

TTL image (local build, ephemeral — expires ~24h from 2026-08-12 08:40 UTC):

ttl.sh/ae-pr92-2e05ed29:24h
ttl.sh/ae-pr92-2e05ed29@sha256:cca205c8aec463e1739604afc5d43f2c6e19c3402d2c2e32ef1b3930266a4baa

Static CGO_ENABLED=0 Go binary (//src/vizier/services/adaptive_export/cmd) on distroless/static-debian12:nonroot.

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-pr92-2e05ed29:24h

Release image — tag release/vizier/v0.14.19-aeprod59, run 31578372120 ✅ (build-release, create-github-release, update-gh-artifacts-manifest all green):

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod59
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:68d38d500b51cd70c91cc9327f1d3e0163ca6f8c4f16bd4290468189fdf1c2c2

k8s/vizier/adaptive_export/kustomization.yaml still pins 0.14.19-aeprod57 — bump to -aeprod59 when this lands.

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent⚠️ aeprod59 (from #92 head 2e05ed2) is not what I need — I verified it has 0 ppid refs in dc_snoop_deploy.pxl (no #94 capture) and 0 PodEnrichPxL in scanner.go (no streaming fix). It would reproduce the regression.

Please build the AE image from branch build/ae-ppid-plus-streaming-darkfix specifically (NOT #92 head). That branch = #94 feat/dc-snoop-ppid-ancestry (5 ppid/pcomm refs ✓) + PR #95 streaming dark-vector fix (2 PodEnrichPxL ✓) — the only combination with both. A TTL image (ttl.sh/...) is fine. Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — being specific, please build the AE (adaptive_export) image from this exact ref, NOT the #92 PR head:

  • branch: build/ae-ppid-plus-streaming-darkfix
  • commit: a9818726ef4b08b4b5ab63892f57559fdf47eef1 (a981872)

This ref = #94 ppid/pcomm dc_snoop capture + PR#95 streaming dark-vector fix. Verify:

  • git show a9818726e:src/vizier/services/adaptive_export/internal/streaming/scanner.go | grep PodEnrichPxL → present (the fix)
  • git show a9818726e:src/vizier/services/adaptive_export/internal/script/presets/dc_snoop_deploy.pxl | grep ppid → present (the capture)

The last build (aeprod59) was #92 head 2e05ed29a which has neither — do not use it. A TTL image for a9818726e is perfect; reply with ttl.sh/... or ghcr.io/.../vizier-adaptive_export_image:<tag> when green. I then run: kubectl set image ds/adaptive-export -n pl adaptive-export=<tag> on rig 6a7be0ac. Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

Re-built from build/ae-ppid-plus-streaming-darkfix @ a9818726e — verified before building: dc_snoop_deploy.pxl has 5 ppid/pcomm refs (#94 ✓) and streaming/scanner.go has 2 PodEnrichPxL refs (#95 ✓). aeprod59 is superseded — don't deploy it.

TTL image (ready now, expires ~24h from 2026-08-12 11:25 UTC):

ttl.sh/ae-ppid-darkfix-a9818726:24h
ttl.sh/ae-ppid-darkfix-a9818726@sha256:64c9c67c563712669e772a1327fef13125578112c20bc828d26842a123853736
kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-ppid-darkfix-a9818726:24h

GHCR image — tag release/vizier/v0.14.19-aeprod60 off the same commit, run 31591683342 in flight (~55 min); ref will be ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod60. I'll post the digest here when it's green.

One note for rig 6a7be0ac: TestLoad_DataPlaneExactReproducible_L1 fails (http_events rows = 0, want 100) on this branch — but it fails identically on #92 head 2e05ed29a, so it's pre-existing, not from #94/#95. Everything else under adaptive_export/... is green.

ConstanzeTU pushed a commit that referenced this pull request Aug 12, 2026
scanner.go imports internal/pxl since the dark-vector enrichment wiring,
but the BUILD deps were never regenerated, so bazel strict-deps failed
//k8s/vizier:vizier_images_push (plain go build was unaffected).

Also runs gazelle over the adaptive_export BUILD files (the new dc_snoop
contract/bench tests and order_chunk_test were missing from srcs, so they
never ran under bazel), drops two named returns in controller.go and
renames deadlineErr to errDeadline.

arc lint is clean over src/vizier/services/adaptive_export/; the 152
remaining repo lint errors are yamllint flow-style findings in the k8s/
and skaffold/ manifests from #92, untouched here.
@ConstanzeTU

Copy link
Copy Markdown
Author

GHCR build update: aeprod60 failed — and it's a real bug on the branch, not CI flake.

//src/vizier/services/adaptive_export/internal/streaming:streaming hit bazel strict-deps:

compilepkg: missing strict dependencies:
  .../streaming/scanner.go: import of "px.dev/pixie/src/vizier/services/adaptive_export/internal/pxl"

The #95 wiring added the internal/pxl import to scanner.go but streaming/BUILD.bazel was never regenerated, so plain go build is fine while //k8s/vizier:vizier_images_push fails. Fixed in 0e1441445 on build/ae-ppid-plus-streaming-darkfix:

release/vizier/v0.14.19-aeprod61 is building from 0e1441445run 31594196141. Posting the GHCR digest here when it's green.

The already-posted TTL image ttl.sh/ae-ppid-darkfix-a9818726:24h is unaffected (built with go build, same AE source) — safe to deploy on rig 6a7be0ac now if you don't want to wait for GHCR.

Not touched: 152 yamllint findings (flow-style braces/indentation) in k8s/vizier/dx/dx-daemon.yaml, k8s/vizier/bootstrap/adaptive_export_deployment.yaml and the skaffold/ manifests from #92 — pre-existing, and I didn't want to reformat your deploy manifests under an image build.

@ConstanzeTU

Copy link
Copy Markdown
Author

✅ Green — run 31594196141 (build-release, create-github-release, update-gh-artifacts-manifest all success).

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod61
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:b4bbf5f50c099518be4114f489b7d971ccf3c02e0c05a99f030444a02e205945

From build/ae-ppid-plus-streaming-darkfix @ 0e1441445 = #94 ppid/pcomm dc_snoop (5 refs in dc_snoop_deploy.pxl) + #95 streaming dark-vector fix (2 PodEnrichPxL in scanner.go) + the strict-deps/gazelle fix.

kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod61

Superseded, do not deploy: aeprod59 (no ppid, no PodEnrichPxL), aeprod60 (failed build).

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — please rebuild the AE (adaptive_export) image from this exact ref (supersedes aeprod61/0e1441445 — that build OOM-wedged my rig):

  • branch: build/ae-ppid-plus-streaming-darkfix
  • commit: 60dc5855bfcd0ef3c2371d1ffd09cb778f67353a (60dc585)

This adds the OOM fix on top of the BUILD.bazel fix. aeprod61 fixed the upid bug but pulled the raw node-wide dc_snoop dentry-lookup firehose (millions of rows) into AE memory under the 1M row cap → node OOM. 60dc5855b extracts a shared pxl.DarkVectorEnrichPxL (pod pid-merge + own-stack exclusions + collapse dc_snoop to distinct processes) used by BOTH the retention builder and the streaming scanner.

Verify before building:

  • git show 60dc5855b:src/vizier/services/adaptive_export/internal/pxl/queryfor.go | grep -c DarkVectorEnrichPxL → 3
  • git show 60dc5855b:src/vizier/services/adaptive_export/internal/streaming/scanner.go | grep -c DarkVectorEnrichPxL → 2
  • git show 60dc5855b:src/vizier/services/adaptive_export/internal/pxl/queryfor.go | grep -c "groupby..pid., .pid_start" → 1 (the firehose collapse / OOM guard)

No new cross-package imports vs 0e1441445, so no further BUILD.bazel regen should be needed. A TTL image is ideal for the quick retest (the GHCR pull backed-off last time). Thank you!

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — ping (no reply/CI run in ~17min). Please build BOTH images from 60dc5855b (branch build/ae-ppid-plus-streaming-darkfix):

  1. TTL image (ttl.sh/...:24h) — I need this NOW for the live OOM/upid/ppid retest (~4 min path).
  2. GHCR release (ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod62 or next) — the durable tag.

Post both refs when ready. This is the OOM-fixed AE (shared DarkVectorEnrichPxL + dc_snoop firehose collapse). Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

TTL image ready — built from 60dc5855b on build/ae-ppid-plus-streaming-darkfix. Markers verified before building: queryfor.goDarkVectorEnrichPxL, scanner.go 2×, firehose-collapse groupby(['pid', 'pid_start'…]) 1×.

ttl.sh/ae-oomfix-60dc5855:24h
ttl.sh/ae-oomfix-60dc5855@sha256:e841a8bb1be28e1bb98684fe1d1db520f6ac9d2f84cc24e3149953ffeba249c1
kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-oomfix-60dc5855:24h

Expires ~2026-08-13 15:40 UTC. go test green on internal/pxl and internal/streaming.

GHCR 0.14.19-aeprod62 is building off the same commit — run 31613259892, ~55 min. Digest follows here when green.

Superseded, do not deploy: aeprod61 (OOM), aeprod59 (no ppid/enrichment), aeprod60 (failed build).

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — one more turn of the loop. Please build BOTH images from b77b80f97 (branch build/ae-ppid-plus-streaming-darkfix, supersedes aeprod62/60dc5855b):

  1. TTL image (ttl.sh/...:24h) — need it now for the retest.
  2. GHCR release (0.14.19-aeprod63 or next) — durable.

What changed: aeprod62/TTL ae-oomfix-60dc5855 deployed clean on my rig — upid error gone, no OOM (node steady 24% mem), DDL + dc_snoop/creds_change tracepoints RUNNING. But the dc_snoop collapse used px.max on the string cols file/t → runtime Could not find UDF max with arguments [STRING], so dc_snoop never compiled. b77b80f97 switches those aggs to px.any (registered for String/Time/Int). One-line fix, same collapse.

Verify: git show b77b80f97:src/vizier/services/adaptive_export/internal/pxl/queryfor.go | grep -c "px.any" → 3. Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

TTL image readyb77b80f97, 3× px.any in queryfor.go verified (the one remaining px.max match is the explanatory comment on line 222, not an agg). internal/pxl tests green.

ttl.sh/ae-pxany-b77b80f9:24h
ttl.sh/ae-pxany-b77b80f9@sha256:92c787732746f313f4a1843f2fd60e6a83e2f3c4964da2028072bcdd64d33c27
kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-pxany-b77b80f9:24h

Expires ~2026-08-13 15:47 UTC.

GHCR 0.14.19-aeprod63 building from the same commit — run 31613928161. Digest here when green. I cancelled the aeprod62 run since you superseded it mid-build — there will be no aeprod62 GHCR tag; aeprod63 is the durable one.

@ConstanzeTU

Copy link
Copy Markdown
Author

GHCR greenrun 31613928161, all jobs success.

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod63
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:b8b70d7ef815dbbcaf0381ca7b2e76720f47fbe4948d99f36fd924c95516634c

Same commit as the TTL image above (b77b80f97 = #94 ppid + #95 streaming fix + strict-deps/gazelle + OOM collapse + px.any).

kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod63

Tag ledger: aeprod63 = current. aeprod61 (OOM), aeprod59 (no ppid/enrichment) superseded; aeprod60 failed to build; aeprod62 cancelled mid-build, no such GHCR tag.

@ConstanzeTU

Copy link
Copy Markdown
Author

@build-agent — this is a DX build (entlein/dx repo, NOT the AE/pixie image). Posting here since this is the channel you watch.

  • repo: entlein/dx
  • branch: build/dx-bench-table-absent
  • commit: 43fbcc22efe2ce2e57c95902e052134019022f43 (43fbcc2)
  • Dockerfile: Dockerfile.dxd · arch: linux/amd64 is enough for the rig

Please post BOTH:

  1. TTL image ttl.sh/…:24h — need it now for the live retest.
  2. a durable tag if easy (docker.io/entlein/dx-daemon:0.4.0-ssotforest-rc3).

(The entlein release-tag CI is out of GitHub-hosted Actions minutes, so it queues forever — that is why I need you to build it.)

This = deployed rc2 + one fix: isTableAbsent() so absent optional tables (dx_mprotect/dx_bpf/http2_messages.beta/stack_trace) stop tripping the bench-blind heuristic. Verify: git show 43fbcc2:cmd/dx-daemon/pxbroker.go | grep -c isTableAbsent → 2. Thanks!

@ConstanzeTU

Copy link
Copy Markdown
Author

DX build answered on entlein/dx#136 — TTL ttl.sh/dxd-43fbcc22:24h (sha256:9fa61092…). Needed an image fix first (90e89aa): the verify stage didn't redeclare ARG TARGETARCH/PX_CLI_VERSION, so the px-CLI download 404'd and every dx-daemon build failed. No durable tag — no Docker Hub creds here and the GHCR token lacks write:packages for a new package.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY e76cd2e
ttl: ttl.sh/ae-uidkeys-e76cd2e5:24h (sha256:f3cafda628598e0bfecefb7bb4dd1bd7a0111a2b37b7e79c2ac9b30c0b9ebc02)
durable: 0.14.19-aeprod84 building — run 32753011076; digest follows
built-from: e76cd2e exactly, no fixes needed
verify: 7 ORDER BY clauses now carry the message payload (req/resp bodies, args, cmds) ✓ · go test ./internal/clickhouse/... green, including the new registration guard ✓ · diff is schema.sql only, 7+/7− ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-uidkeys-e76cd2e5:24h

TTL expires ~2026-08-25 17:00 UTC.

No need for the correction — I had already spotted that c557bb0 is a dx sha and built e76cd2e53, since it is the only commit on fix/ae-honour-tight-query-window that touches the ORDER BY clauses. Both halves were building before your follow-up landed.

Deploy them together, and recreate the tables — this pair is not backward compatible. dx rc14 changes what goes into a unique_id (the payload is now part of the key) and AE aeprod84 changes the ORDER BY to match. Consequences if they diverge:

  • dx rc14 + AE ≤ aeprod83: dx stamps new-style unique_ids, the base tables still collapse rows on the old key → the edge×base join fans out again, which is the 1.19M-vs-280842 pathology you just measured, not a fix for it.
  • AE aeprod84 + dx ≤ rc13: new ORDER BY, old unique_ids — rows dedupe differently from how they are keyed, so the bridge quietly loses joins.
  • Either, without recreating the tables: CREATE TABLE IF NOT EXISTS will not restate ORDER BY on an existing table, so a live forensic_db keeps the old sort key regardless of the image. Fresh CH or DROP TABLE on the 7 affected tables, as you already noted.

dx rc14's TTL is on dx#136 once its build finishes.

Numbering note: aeprod83's run is still in flight, so 82, 83 and 84 are now all building at once. 84 is a superset of 83 (e76cd2e53 descends from 9352a8251), so pin 84 and skip the other two.

@ConstanzeTU

Copy link
Copy Markdown
Author

aeprod82, 83 and 84 all green. Three tags landed in quick succession, so here they are together with the one to pin.

Pin this one — 0.14.19-aeprod84 (run 32753011076):

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod84
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:8558372779527450a27edfe0b93aa28bdc019e18e256ab27846c90ae3a757cd8
kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod84

The other two published fine and are strict subsets — no reason to deploy them, listed only so the digests are on record:

tag commit digest what it adds
aeprod82 5bc2c1df7 sha256:8af49744c402… dx_ord__* event_time as DateTime64
aeprod83 9352a8251 sha256:946dbbe5d6ad… tight /query window, cql/mongodb/creds_change bridge, phantom tables pruned, + the view registration fix
aeprod84 e76cd2e53 sha256:855837277952… ORDER BY mirrors dx UID keys, + everything above, + the schema-registration guard test

84 descends from 83 descends from 82, so pinning 84 gets all of it.

Pair with dx rc15, not rc14 — rc14 has the blocking TIME64NS compare defect (dx#136); its Hub tag published before that was caught, so do not pin it. rc15 is ttl.sh/dxd-6948420:24h now, durable building.

And the recreate step still applies before any of this means anything on an existing forensic_db: the 7 protocol tables need dropping (ORDER BY change) and the 8 dx_ord__* views too (DateTime64 change), or start from a fresh CH.

…ow's

The 11 dx_ord__ views projected c.event_time (the evidence row's kernel CAPTURE
time). The px connector windows on event_time, and aeprod82 made that filter real
(DateTime64, not the old ns-vs-seconds no-op). Evidence always precedes its
alert, so a window around a fresh anomaly dropped its own (older) evidence — all
panels empty.

Project the ORDER's time instead: fromUnixTimestamp64Nano(toInt64(e.event_time))
(the edge carries ref.T, populated non-zero). ts/row_time keep the row's real
capture time for display/ordering. Now a window around the anomaly returns its
evidence regardless of when the packet/syscall was captured.

Validated live: R0011 DNS-egress order, -15m window -> conn_stats 10, dns 34,
dc_snoop 495 (was 0/0/0).

In-place upgrade: DROP the 11 dx_ord__ views before apply (CREATE IF NOT EXISTS
won't replace); fresh rigs unaffected.
@ConstanzeTU

Copy link
Copy Markdown
Author

build dc7230ff3 as aeprod85 (TTL + durable).

Fixes empty evidence panels. The 11 dx_ord__* views projected the base row's event_time (kernel capture time); the px connector windows on event_time, and aeprod82 made that filter real. Evidence always precedes its alert, so a window around a fresh anomaly dropped its own older evidence → all panels empty.

Now project the ORDER's time: fromUnixTimestamp64Nano(toInt64(e.event_time)) (edge carries ref.T). ts/row_time keep the real capture time.

Validated live (rig, new views applied by hand): R0011 DNS-egress order at -15m → conn_stats 10, dns 34, dc_snoop 495 (was 0/0/0).

In-place upgrade needs DROP of the 11 dx_ord__ views before apply (CREATE IF NOT EXISTS won't replace). Fresh rigs fine.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY dc7230f
ttl: ttl.sh/ae-ordtime-dc7230ff:24h (sha256:0ddc090a8a1aaa4827a4ab9d161d3c3bf4543b9a10f9a32376c381c411ccfbc3)
durable: 0.14.19-aeprod85 building — run 32836409319; digest follows
built-from: dc7230f exactly, no fixes needed
verify: all 11 dx_ord__* views project fromUnixTimestamp64Nano(toInt64(e.event_time)) — the order's time, from the edge ✓ · every one keeps both ts and row_time at the real capture time ✓ · go test ./internal/clickhouse/... green ✓ · diff is schema.sql only, 11+/11− ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-ordtime-dc7230ff:24h

TTL expires ~2026-08-26 08:40 UTC. Confirmed on the in-place upgrade: the 11 views must be dropped first, as you noted —

DROP VIEW IF EXISTS forensic_db.dx_ord__conn_stats;    DROP VIEW IF EXISTS forensic_db.dx_ord__redis_events;
DROP VIEW IF EXISTS forensic_db.dx_ord__http_events;   DROP VIEW IF EXISTS forensic_db.dx_ord__dns_events;
DROP VIEW IF EXISTS forensic_db.dx_ord__pgsql_events;  DROP VIEW IF EXISTS forensic_db.dx_ord__mysql_events;
DROP VIEW IF EXISTS forensic_db.dx_ord__cql_events;    DROP VIEW IF EXISTS forensic_db.dx_ord__mongodb_events;
DROP VIEW IF EXISTS forensic_db.dx_ord__creds_change;  DROP VIEW IF EXISTS forensic_db.dx_ord__dc_snoop;
DROP VIEW IF EXISTS forensic_db.dx_ord__stack_trace;

Three consequences of event_time now meaning the order's time in these views, worth being deliberate about since the column keeps its old name:

  1. It no longer means what it means in the base tables. forensic_db.pgsql_events.event_time is capture time; forensic_db.dx_ord__pgsql_events.event_time is the anomaly's time. Anything that joins or unions across the two, or a reviewer reading a column list, will read the same name as the same thing. ts/row_time carry the truth, so the data is fine — it is the naming that will mislead. order_time would say what it is, at the cost of the connector no longer auto-windowing on it, which is the whole point. Probably worth a comment in the view header at minimum.

  2. Every row in an order now shares one event_time. Sorting evidence within an order by event_time is a no-op — panels that want chronological order must sort on row_time (or ts). Worth checking the vis specs; a graph that relies on edge ordering would silently show insertion order instead.

  3. Any future keyset pagination on event_time over these views would hang. A cursor that advances by WHERE event_time > <last seen> never advances when every row is equal — it would re-read the same page forever, or peel the whole group each time. Not live today (pagefix1 was reverted, so the connector is back to LIMIT/OFFSET), but if that work returns it must key on row_time for these views, not event_time. Flagging now because it would present as a hang, not an error.

@ConstanzeTU

Copy link
Copy Markdown
Author

aeprod85 greenrun 32836409319, every job success.

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod85
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:1afbf7301b052935fd25e3d9d1c4624d2852e44d42b33046523a9a89edd256da

From dc7230ff3dx_ord__* window on the order's time.

kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod85

Bump k8s/vizier/adaptive_export/kustomization.yaml to 0.14.19-aeprod85.

Current pair: AE aeprod85 + dx rc17 (docker.io/entlein/dx-daemon:0.5.0-keepset-rc17).

Tag ledger: aeprod85 = current AE · 84 = UID-key ORDER BY · 83, 82 superseded · 68 unsigned, do not pin · 73, 64 failed.

Don't forget the 11 DROP VIEW statements from the previous comment before restarting AE on an existing forensic_db — this build is inert without them.

…nels

ORDERS: add ts (order/anomaly datetime) so it's visible + sortable — pulled
from dx_kubescape_mitre, which gains ts = toString(fromUnixTimestamp64Nano(
event_time)) (schema.sql; live-updatable). Drop the constant namespace column
from http/dns/pgsql/mysql/dc_snoop panels (kept on conn/redis). Native
header-click sort covers 'sort on TS'.
The dx/ script is the maintained SOC dashboard now; remove the old px/
sibling. Its only unique view deps (dx_src__kubescape_logs,
dx_ord__stack_trace) are left in AE schema for now — decommission
separately once confirmed unused.
New Live-UI script (does not touch dx/evidence_graph). Seed from pods with
an alert >= min_sev (editable knob; severity is data-driven), then overlay
every sensor's edges for the incident from a UNION view dx_fullchain_edges
(kubescape rule->pod, conn egress pod->ip, dns name->ip, pgsql pod->query).
Deliberately not keyed on one order_id (the read and send split across
anomaly windows); correlates by pod + window + shared IP. Sparse: a missing
sensor drops its edges, never the graph.

dx_fullchain_edges is a NEW view (created live on the rig; not yet in AE
schema.sql). v1 renders the alert layer; the egress/DNS/SQL overlay fills in
once Pixie attributes the incident pod's exfil protocol traffic (capture fix
in flight) — the connects/resolves edges currently land on coredns/infra,
and pgsql isn't captured (TLS). v2 = upid->pid bridge + IP-eqclass +
sender/receiver stitching for true cross-pod chains.
@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-REQUEST c1b96cfe7 (branch fix/ae-protocol-export-pxexport) — adaptive_export, next tag aeprod86. TTL first please (fast), durable after.

= aeprod85 + one schema addition: dx_kubescape_mitre gains ts = toString(fromUnixTimestamp64Nano(toInt64(event_time))) (schema.sql). The shipped dx/evidence_graph ORDERS panel selects this ts, so aeprod85 clusters throw "Column 'ts' not found" — this fixes it durably. Keeps your order-time dx_ord__ fix + cql/mongodb/creds unique_id bridges (verified present at the tip). go test ./internal/clickhouse/... green expected. Please post the TTL image ref ASAP; rig 6a8c58b6 standing by.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY c1b96cf (TTL first, as asked — durable follows)
ttl: ttl.sh/ae-mitrets-c1b96cfe:24h (sha256:42e4eea08fda727a8e4f24c35b0d2911dabdcfad5f63eb4d4f9d55f4ec752933)
built-from: c1b96cf exactly, no fixes needed
verify: dx_kubescape_mitre now selects toString(fromUnixTimestamp64Nano(toInt64(event_time))) AS ts ✓ · aeprod85 (dc7230ff3) is an ancestor, so the order-time dx_ord__ fix and the cql/mongodb/creds bridges are all still in ✓ · go test ./internal/clickhouse/... green ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-mitrets-c1b96cfe:24h

TTL expires ~2026-08-26 13:40 UTC. Durable 0.14.19-aeprod86 starting now; digest to follow.

One correction to the description, since it affects what lands on the rig: this is not aeprod85 + one schema addition. Three commits sit in that range —

commit what
a3ea1bd61 the ts column (+ drops namespace from the protocol panels)
634269f58 decommissions px/dx_evidence_graph — deletes its pxl, vis.json, manifest and README
c1b96cfe7 adds the new dx/fullchain script

The schema diff really is the one line you described. But the script bundle changes substantially: px/dx_evidence_graph is removed outright (195-line vis.json and all), and dx/fullchain is new. If anything still points at the px/ script — a saved link, a dashboard, muscle memory — it will 404 after this rolls, and that is a update-script-bundle effect rather than an image one. Worth knowing before you deploy on 6a8c58b6 rather than discovering it mid-demo.

The ts fix also needs the view dropped, same as every schema change on an existing forensic_db:

DROP VIEW IF EXISTS forensic_db.dx_kubescape_mitre;

then restart AE. Without it the ORDERS panel keeps throwing Column 'ts' not found and this build will look like it did nothing.

@ConstanzeTU

Copy link
Copy Markdown
Author

aeprod86 greenrun 32857819638, every job success.

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod86
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:208df465615acf7d7684b76e16e5910accafa247372ff88e30acb0963fd6b2e3

From c1b96cfe7dx_kubescape_mitre.ts, plus the px/dx_evidence_graph decommission and the new dx/fullchain script noted above.

kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod86

Bump k8s/vizier/adaptive_export/kustomization.yaml to 0.14.19-aeprod86, and remember DROP VIEW IF EXISTS forensic_db.dx_kubescape_mitre; before the restart or the ORDERS panel keeps failing on the missing ts.

Current pair: AE aeprod86 + dx rc20 (972ee26, building now on dx#136).

Tag ledger: aeprod86 = current AE · 85 = order-time dx_ord__ · 84 = UID-key ORDER BY · 68 unsigned, do not pin · 73, 64 failed.

dx stamps culprit_key = ns/pod/RootPID on each order; orders sharing it are one
actor's steps. dx_cases joins orders to dx_kubescape_mitre so a case's steps
(read + exfil + spawns) surface under one culprit_key, mitre/severity attached
for UI colouring. Registered in Known/OperatorOwnedTables; contract test updated.
@ConstanzeTU

Copy link
Copy Markdown
Author

build d68a2571a as aeprod87 (branch chore/ae-culprit-key, off aeprod86). Adds dx_orders.culprit_key + dx_cases view (order→case meta-grouping). Pairs with dx rc22. clickhouse tests green.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY d68a257
ttl: ttl.sh/ae-culprit-d68a2571:24h (sha256:71a3ec06838bbacc2b5db4bdc0184d54a5f4c9953a72d78071c0200089629576)
durable: 0.14.19-aeprod87 building — run 32933246660; digest follows
built-from: d68a257 exactly, no fixes needed
verify: culprit_key in schema.sql (5 refs) ✓ · dx_cases registered in all three places — schema.sql, KnownTables, OperatorOwnedTables ✓ · aeprod86 (c1b96cfe7) is an ancestor ✓ · go test ./internal/clickhouse/... green ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-culprit-d68a2571:24h

TTL expires ~2026-08-27 10:20 UTC. Pairs with dx rc22 (4fb5946, building on dx#136).

Nice to report: dx_cases was registered correctly on the first pass — the schema-registration guard passed rather than caught anything. That is the third view added since it landed; the two before it (dx_base__dc_snoop, the dx_ord__{cql,mongodb,creds_change} trio) both had to be found by hand on a rig, so the guard has now paid for itself twice over and gone quiet, which is what a good drift test looks like.

Two things for the rig:

  • dx_orders gains a column, so it needs recreatingCREATE TABLE IF NOT EXISTS will not add culprit_key to an existing table, and dx_cases selects it. On an existing forensic_db: DROP TABLE IF EXISTS forensic_db.dx_orders; (plus DROP VIEW IF EXISTS forensic_db.dx_cases; if a stale one exists) before the restart. Fresh CH is unaffected.
  • Order of deploy does not matter here, unlike the aeprod84/rc14 pair: dx rc22 stamping culprit_key into a column AE has not created yet just means the value is dropped, and AE creating the column before dx stamps it means empty strings. Neither corrupts anything, so you can roll them in either order — just expect dx_cases to be empty until both are in.

@ConstanzeTU

Copy link
Copy Markdown
Author

Both durables green — the culprit_key pair is fully published.

AE aeprod87 (run 32933246660):

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod87
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:897a24afe5455139bc3a5be91c79f89d523a9373d4cecb1cca21ed2189a4d731

dx rc22 (run 32933244270):

docker.io/entlein/dx-daemon:0.5.0-keepset-rc22
docker.io/entlein/dx-daemon@sha256:6fa676ffd25f2d4c686b42f8006794b368ee6a1619e7b8fcb4c8e094be24f613
kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod87
kubectl -n honey set image ds/dx-daemon dx-daemon=docker.io/entlein/dx-daemon:0.5.0-keepset-rc22

Current pair: AE aeprod87 + dx rc22. Before the restart on an existing forensic_db: DROP TABLE IF EXISTS forensic_db.dx_orders; and DROP VIEW IF EXISTS forensic_db.dx_cases; — the new culprit_key column cannot arrive any other way.

Ledgers — AE: 87 = current · 86 = mitre ts · 85 = order-time dx_ord__ · 68 unsigned, do not pin · 73, 64 failed. dx: rc22 = current · rc21…rc15 superseded · rc14 do NOT deploy.

Adds cases() + a top CASES graph widget: culprit (ns/pod/RootPID) fans out to
each step it took (pod:rule, mitre-labelled, severity-coloured). Reads dx_cases
so one actor's read + exfil + spawns render as one campaign instead of 40
disconnected order dots. Order-link drills into any step. Existing per-order
panels shifted down; unchanged.
@ConstanzeTU

Copy link
Copy Markdown
Author

Cloud bundle update needed for the CASES view (P3): src/pxl_scripts/dx/evidence_graph/{evidence_graph.pxl,vis.json} on branch chore/ae-culprit-key add a cases() function + a top CASES graph widget (culprit→steps, grouped by culprit_key). This is baked into the cloud proxy /bundle-oss.json, so it needs a release/cloud/v* build to surface in the Live UI. Pairs with dx rc22 + AE aeprod87 (dx_orders.culprit_key + dx_cases view). Grouping validated CH-direct (culprit separation clean); UI render pending the cloud bake.

@ConstanzeTU

Copy link
Copy Markdown
Author

Cloud bundle building — release/cloud/v0.0.46-pre-v0.0 from 017934e86, run 32938859534.

verify: cases() present in dx/evidence_graph/evidence_graph.pxl ✓ · CASES widget in its vis.json ✓ · tagged the CASES commit itself (017934e86), which sits on top of d68a2571a — so the bundle carries the culprit_key schema too, not just the script.

Numbering follows the existing line: v0.0.45 was the last cloud tag (2026-08-25 21:40), so this is v0.0.46-pre-v0.0. First cloud release I have cut — say the word if you would rather own that tag yourself in future; it is a different pipeline from vizier/dx and it publishes the whole cloud image set, not just the bundle.

Deployment shape, since this is the piece that surfaces in the Live UI rather than on the rig: the bundle is baked into the cloud proxy image, so the CASES view appears once your cloud deployment is rolled onto the new image — a vizier/AE restart will not surface it, and neither will the rig. The three pieces have to line up:

piece tag carries
AE 0.14.19-aeprod87 dx_orders.culprit_key, dx_cases view
dx 0.5.0-keepset-rc22 stamps culprit_key on orders
cloud v0.0.46-pre-v0.0 cases() + the CASES widget in /bundle-oss.json

Missing the cloud piece is the one that looks most like a bug — the data is correct in ClickHouse, the panel simply is not in the UI's script list. Which matches what you are seeing now.

Digest and the run result follow when it finishes; cloud builds take longer than vizier ones.

@ConstanzeTU

Copy link
Copy Markdown
Author

Cloud v0.0.46-pre-v0.0 greenrun 32938859534, images published.

ghcr.io/k8sstormcenter/cloud-proxy_server_image:0.0.46-pre-v0.0
ghcr.io/k8sstormcenter/cloud-proxy_server_image@sha256:b2f21d9dbd407aa7deed2bc3e8579fcfba19031369e57042b713cd6968c3125f

(plus the rest of the cloud image set from the same tag — the proxy is the one that carries /bundle-oss.json and therefore the CASES view.)

Create Release on Github shows as skipped, which I checked rather than assumed: v0.0.45's run skipped it identically, so that is this workflow's normal shape and not a failure of this build. The images are what matter here.

All three pieces are now published:

piece tag digest
AE 0.14.19-aeprod87 sha256:897a24af…
dx 0.5.0-keepset-rc22 sha256:6fa676ff…
cloud v0.0.46-pre-v0.0 sha256:b2f21d9d…

Roll the cloud deployment onto the new proxy image and the CASES widget should appear in the Live UI script list; the rig side is already correct once dx_orders has been recreated.

conn_stats gains remote_pod (dx-resolved peer pod, non-key). dx_case_links joins
an order's conn_stats.remote_pod to the culprit on that peer pod → directed
from_culprit->to_culprit edge. Links the sink's exfil-receipt culprit back to the
attacker's culprit that opened the connection (the one cross-pod under-match P0
left).
@ConstanzeTU

Copy link
Copy Markdown
Author

build 15e733fdb as aeprod88 (branch chore/ae-culprit-key). Adds conn_stats.remote_pod + dx_case_links view — the cross-pod case bridge (links sink's R0012 culprit back to the attacker via the connection). Pairs with dx rc23. clickhouse tests green.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY 15e733f
ttl: ttl.sh/ae-caselinks-15e733fd:24h (sha256:edf7db40dded7d534cdf5a138df811762a032cd9f56f04b729933c840fa20483)
durable: 0.14.19-aeprod88 building — run 32940508745; digest follows
built-from: 15e733f exactly, no fixes needed
verify: conn_stats carries remote_pod ✓ · dx_case_links registered in schema.sql + KnownTables + OperatorOwnedTables ✓ (guard passed again — second clean one in a row) · aeprod87 (d68a2571a) is an ancestor ✓ · go test ./internal/clickhouse/... green ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-caselinks-15e733fd:24h

TTL expires ~2026-08-27 12:20 UTC. Pairs with dx rc23 (e2e4fe0, building on dx#136).

Recreate list is growing — conn_stats is the expensive one this time. remote_pod is a new column, so on an existing forensic_db:

DROP TABLE IF EXISTS forensic_db.conn_stats;   -- new column: remote_pod
DROP VIEW  IF EXISTS forensic_db.dx_ord__conn_stats;   -- selects from it
DROP VIEW  IF EXISTS forensic_db.dx_case_links;        -- if a stale one exists

Unlike dx_orders last time, conn_stats holds captured evidence — dropping it discards whatever is already stored there, not just a bridge table. If the rig has runs you care about, export before dropping, or start from a fresh CH.

This is the sixth build in a row gated on a manual drop, and now one that costs data. The Apply-side recreate step I offered back on aeprod80 would handle the view half automatically (views are derived, always safe to drop and rebuild) and could log a clear "column X missing on table Y — recreate required" for the table half rather than silently doing nothing. Roughly 30 lines. Worth doing before the next column lands, I think — say the word and it goes in the next AE build.

@ConstanzeTU

Copy link
Copy Markdown
Author

Both durables green — the cross-pod case bridge is published.

AE aeprod88 (run 32940508745):

ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod88
ghcr.io/k8sstormcenter/vizier-adaptive_export_image@sha256:3c4e06008f69ec110926cb2e3ac98ecfdb2a6d96afae5cad9de91471912008ae

dx rc23 (run 32940504721):

docker.io/entlein/dx-daemon:0.5.0-keepset-rc23
docker.io/entlein/dx-daemon@sha256:91b5596d7c23c74afd0de51fb6b0a79fd54b39085646251227fbb63e4e63369c
kubectl set image deployment/adaptive-export -n pl adaptive-export=ghcr.io/k8sstormcenter/vizier-adaptive_export_image:0.14.19-aeprod88
kubectl -n honey set image ds/dx-daemon dx-daemon=docker.io/entlein/dx-daemon:0.5.0-keepset-rc23

Current pair: AE aeprod88 + dx rc23. Before restarting on an existing forensic_db — and note the first one throws away captured rows, not just a derived table:

DROP TABLE IF EXISTS forensic_db.conn_stats;         -- new column: remote_pod (LOSES stored evidence)
DROP VIEW  IF EXISTS forensic_db.dx_ord__conn_stats;
DROP VIEW  IF EXISTS forensic_db.dx_case_links;

Ledgers — AE: 88 = current · 87 = culprit_key/dx_cases · 86 = mitre ts · 68 unsigned, do not pin · 73, 64 failed. dx: rc23 = current · rc22…rc15 superseded · rc14 do NOT deploy. Cloud: v0.0.46-pre-v0.0 carries the CASES widget.

…hain_edges views

Lands the three dashboard views in schema.sql so they exist on any cluster (they
were rig-live only, breaking shipped scripts on fresh rigs). dx_alerts is the
thin generic flatten of kubescape_logs; dx/breakout PxL now derives target/kind
in PxL from it (story logic out of the DB). dx_breakout_story + dx_fullchain_edges
kept for the shipped v0.0.45 dashboards. Registered in apply.go/ddl.go/apply_test.go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011drju5k6V9dRaJnoiZLSLv
@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-REQUEST c4357a62b (branch fix/ae-protocol-export-pxexport) — adaptive_export, next tag aeprod87. TTL first please (fast), durable after.

= aeprod86 + three new CREATE VIEW IF NOT EXISTS in schema.sql, registered in apply.go + ddl.go + apply_test.go (mirrors how dx_dns_resolve was added):

  • dx_alerts — generic thin flatten of kubescape_logs (pod/namespace/rule/message/sev).
  • dx_breakout_story — runtime-breakout edges (needed by the shipped dx/breakout cloud image v0.0.45).
  • dx_fullchain_edges — cross-sensor UNION (needed by shipped dx/fullchain).

All three are VIEWs over existing tables (kubescape_logs/conn_stats/dns_events/pgsql_events) — deferred-view path, no table changes. go test ./internal/clickhouse/... should be green (TrailingOperatorTables want[] + registration lists updated in lockstep). Please post the TTL ref ASAP — a deployed cloud image is blocked on these views existing. Rig 6a8c58b6→now 6a8d99e4 standing by.

@ConstanzeTU

Copy link
Copy Markdown
Author

BUILD-READY c4357a6tagged aeprod89, not 87 (TTL first as asked; durable starting now)
ttl: ttl.sh/ae-storyviews-c4357a62:24h (sha256:7bf99cc7750ae60ac3f203fde4f7a93e50ddacfa6b6c01bb44cb0a4fb05ad9a1)
built-from: c4357a6 exactly, no fixes needed
verify: dx_alerts, dx_breakout_story, dx_fullchain_edges each registered in schema.sql + KnownTables + OperatorOwnedTables ✓ (registration guard passed — third clean one) · go test ./internal/clickhouse/... green ✓

kubectl set image deployment/adaptive-export -n pl adaptive-export=ttl.sh/ae-storyviews-c4357a62:24h

TTL expires ~2026-08-27 13:40 UTC — rig 6a8d99e4 unblocked, the cloud image's views will exist once this is up.

On the number: 87 and 88 are both taken and published. You cut those an hour ago on chore/ae-culprit-key (87 = culprit_key/dx_cases, 88 = conn_stats.remote_pod/dx_case_links), both green, both deployed-ready. Burned numbers are never reused, so this is aeprod89.

The good news, which I checked before building rather than assuming: c4357a62b contains both of them. d68a2571a and 15e733fdb are ancestors, so aeprod89 is a strict superset — the two branches have converged and you lose nothing by jumping from 88 to 89. Had they diverged, deploying this would have silently reverted culprit_key and remote_pod, taking dx_cases and dx_case_links down with them; worth the 20 seconds to confirm.

Recreate: all three are new views over existing tables, so nothing to drop for these — but if you have not yet done the aeprod88 step on 6a8d99e4, conn_stats still needs recreating for remote_pod, and that one discards stored rows. New rig, so probably moot.

Fresh-rig reproducibility: the deploy pins were 8/12 builds stale (AE aeprod81,
dx rc11), so a fresh rig got none of the case-grouping / order-time-view /
cross-pod-bridge work. Bump AE 0.14.19-aeprod89 (culprit_key + dx_cases +
dx_case_links + story views) and dx rc23. Add DX_NARROW_WINDOW_{BEFORE,AFTER}_MS
=150: the server-side protocol capture (e.g. an exfil POST seen on the sink)
lands just AFTER the egress anomaly, so a backward-only window drops it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants