@pleri/olam-cli 0.1.159 → 0.1.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +11 -0
  2. package/dist/agent-stream/agent-sdk-to-chunks.js +3 -0
  3. package/dist/agent-stream/driver-runner.js +9 -4
  4. package/dist/agent-stream/host-driver-launch.js +48 -0
  5. package/dist/commands/bootstrap.d.ts +15 -0
  6. package/dist/commands/bootstrap.d.ts.map +1 -1
  7. package/dist/commands/bootstrap.js +30 -1
  8. package/dist/commands/bootstrap.js.map +1 -1
  9. package/dist/commands/flywheel/check-persona-skeleton.d.ts +30 -2
  10. package/dist/commands/flywheel/check-persona-skeleton.d.ts.map +1 -1
  11. package/dist/commands/flywheel/check-persona-skeleton.js +143 -6
  12. package/dist/commands/flywheel/check-persona-skeleton.js.map +1 -1
  13. package/dist/commands/flywheel/diversity-check.d.ts +12 -2
  14. package/dist/commands/flywheel/diversity-check.d.ts.map +1 -1
  15. package/dist/commands/flywheel/diversity-check.js +56 -6
  16. package/dist/commands/flywheel/diversity-check.js.map +1 -1
  17. package/dist/commands/flywheel/index.d.ts.map +1 -1
  18. package/dist/commands/flywheel/index.js +2 -0
  19. package/dist/commands/flywheel/index.js.map +1 -1
  20. package/dist/commands/flywheel/install-shims.d.ts +36 -3
  21. package/dist/commands/flywheel/install-shims.d.ts.map +1 -1
  22. package/dist/commands/flywheel/install-shims.js +118 -7
  23. package/dist/commands/flywheel/install-shims.js.map +1 -1
  24. package/dist/commands/flywheel/k10-measure.d.ts +12 -2
  25. package/dist/commands/flywheel/k10-measure.d.ts.map +1 -1
  26. package/dist/commands/flywheel/k10-measure.js +55 -6
  27. package/dist/commands/flywheel/k10-measure.js.map +1 -1
  28. package/dist/commands/flywheel/migrate-overlays.d.ts +115 -0
  29. package/dist/commands/flywheel/migrate-overlays.d.ts.map +1 -0
  30. package/dist/commands/flywheel/migrate-overlays.js +766 -0
  31. package/dist/commands/flywheel/migrate-overlays.js.map +1 -0
  32. package/dist/commands/flywheel/sanitize-persona-output.d.ts +33 -2
  33. package/dist/commands/flywheel/sanitize-persona-output.d.ts.map +1 -1
  34. package/dist/commands/flywheel/sanitize-persona-output.js +94 -6
  35. package/dist/commands/flywheel/sanitize-persona-output.js.map +1 -1
  36. package/dist/commands/memory/index.d.ts.map +1 -1
  37. package/dist/commands/memory/index.js +2 -0
  38. package/dist/commands/memory/index.js.map +1 -1
  39. package/dist/commands/memory/install-hooks.d.ts +22 -0
  40. package/dist/commands/memory/install-hooks.d.ts.map +1 -0
  41. package/dist/commands/memory/install-hooks.js +156 -0
  42. package/dist/commands/memory/install-hooks.js.map +1 -0
  43. package/dist/commands/skills-doctor.js +2 -2
  44. package/dist/commands/skills-doctor.js.map +1 -1
  45. package/dist/commands/skills-source.d.ts.map +1 -1
  46. package/dist/commands/skills-source.js +10 -0
  47. package/dist/commands/skills-source.js.map +1 -1
  48. package/dist/commands/skills.d.ts.map +1 -1
  49. package/dist/commands/skills.js +169 -1
  50. package/dist/commands/skills.js.map +1 -1
  51. package/dist/image-digests.json +7 -7
  52. package/dist/index.js +4361 -1768
  53. package/dist/lib/bootstrap-kubernetes.d.ts +42 -0
  54. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -0
  55. package/dist/lib/bootstrap-kubernetes.js +287 -0
  56. package/dist/lib/bootstrap-kubernetes.js.map +1 -0
  57. package/dist/lib/config.d.ts.map +1 -1
  58. package/dist/lib/config.js +6 -1
  59. package/dist/lib/config.js.map +1 -1
  60. package/dist/lib/flywheel-probes.d.ts +58 -0
  61. package/dist/lib/flywheel-probes.d.ts.map +1 -0
  62. package/dist/lib/flywheel-probes.js +163 -0
  63. package/dist/lib/flywheel-probes.js.map +1 -0
  64. package/dist/lib/shim-generator.d.ts +51 -0
  65. package/dist/lib/shim-generator.d.ts.map +1 -0
  66. package/dist/lib/shim-generator.js +88 -0
  67. package/dist/lib/shim-generator.js.map +1 -0
  68. package/dist/lib/skills-apply-overlays.d.ts +35 -0
  69. package/dist/lib/skills-apply-overlays.d.ts.map +1 -0
  70. package/dist/lib/skills-apply-overlays.js +243 -0
  71. package/dist/lib/skills-apply-overlays.js.map +1 -0
  72. package/dist/mcp-server.js +1106 -453
  73. package/hermes-bundle/version.json +1 -1
  74. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  75. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  76. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  77. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  78. package/host-cp/k8s/manifests/memory-service/30-configmap.yaml +11 -0
  79. package/host-cp/k8s/manifests/memory-service/35-configmap-iii-config.yaml +76 -0
  80. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +11 -1
  81. package/host-cp/observability/grafana-port-forward.sh +273 -0
  82. package/host-cp/observability/kyverno-cardinality-mutate.sh +452 -0
  83. package/host-cp/observability/loki-ingest.sh +243 -0
  84. package/host-cp/observability/prom-no-double-grafana.sh +301 -0
  85. package/host-cp/src/crystallize-planning.mjs +261 -0
  86. package/host-cp/src/plan-chat-service.mjs +84 -2
  87. package/host-cp/src/planning-sessions.mjs +270 -0
  88. package/package.json +1 -1
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env bash
2
+ # kyverno-cardinality-mutate.sh — Phase C C8 follow-up e2e smoke test.
3
+ #
4
+ # Verifies that the Kyverno ClusterPolicy
5
+ # `enforce-cardinality-labeldrop` mutates incoming ServiceMonitor and
6
+ # PodMonitor objects at admission time, regardless of authorship,
7
+ # closing codex's "policy by convention" gap on PR #783.
8
+ #
9
+ # Test approach:
10
+ # 1. helm-install Kyverno (pinned 3.8.1) into the `kyverno` namespace.
11
+ # 2. Apply the ClusterPolicy.
12
+ # 3. POSITIVE test: apply ServiceMonitor `kyverno-mutate-positive-test`
13
+ # with selector `app: kyverno-mutate-positive-test` (no backing Service)
14
+ # and NO metricRelabelings; assert Kyverno mutated it; delete immediately.
15
+ # 4. IDEMPOTENCY test: apply ServiceMonitor `kyverno-mutate-idempotency-test`
16
+ # with selector `app: kyverno-mutate-idempotency-test` (different non-existent
17
+ # label) and the labeldrop already present; assert count stays at 1; delete.
18
+ # 5. SCRAPE-VERIFICATION test: deploy synthetic `kyverno-emitter` (Service +
19
+ # Deployment + ConfigMap) + dedicated ServiceMonitor `kyverno-emitter-sm`
20
+ # applied WITHOUT metricRelabelings; assert Kyverno mutates the SM at admission;
21
+ # wait for pod Ready; poll Prometheus for http_requests_total; assert
22
+ # world_id label is ABSENT.
23
+ #
24
+ # Key design decision: POSITIVE and IDEMPOTENCY tests use selectors that match
25
+ # no real Service, so they are isolated from each other and from the SCRAPE test.
26
+ # A single dedicated SM (`kyverno-emitter-sm`) owns the emitter endpoint, so
27
+ # prometheus-operator can reliably reconcile exactly one scrape config for it.
28
+ # Root cause of the prior failure (PR #828 CI run 26239574154): two SMs
29
+ # (naive-violator + pre-armoured-violator) competed for the same
30
+ # `app: kyverno-emitter` Endpoints; operator never reconciled either.
31
+ #
32
+ # Pre-conditions:
33
+ # - kube-prometheus-stack installed (cardinality-drop.sh ran).
34
+ # - kubectl context set to a live cluster; helm + jq + curl available.
35
+ #
36
+ # Idempotency: kubectl apply is idempotent; helm upgrade --install is
37
+ # idempotent. Cleanup trap removes synthetic resources on exit. The
38
+ # ClusterPolicy + Kyverno install are LEFT in the cluster (permanent
39
+ # C8 fixtures).
40
+ #
41
+ # Refs: docs/plans/k3s-ingress-observability/phase-c-tasks.md — C8
42
+ # codex review on PR #783 ("policy by convention" finding)
43
+ # PR #828 CI run 26239574154 (competing-SM root cause)
44
+
45
+ set -euo pipefail
46
+
47
+ KYVERNO_VERSION="3.8.1"
48
+ KYVERNO_NAMESPACE="kyverno"
49
+ TEST_NAMESPACE="monitoring"
50
+ PROM_LOCAL_PORT="9092" # 9090, 9091 may be in use by sibling Phase C scripts
51
+ PF_BIND_SECONDS=5
52
+ TARGET_DISCOVERY_TIMEOUT=180
53
+ SCRAPE_POLL_INTERVAL=10
54
+
55
+ log() { printf '[kyverno-mutate] %s\n' "$*" >&2; }
56
+ fail() { printf '[kyverno-mutate] FAIL: %s\n' "$*" >&2; exit 1; }
57
+
58
+ REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || pwd)"
59
+
60
+ # -------------------------------------------------------------------------
61
+ # Cleanup trap — kill port-forwards; remove synthetic resources on exit.
62
+ # Kyverno chart + ClusterPolicy stay (permanent C8 fixtures).
63
+ # -------------------------------------------------------------------------
64
+ PROM_PF_PID=""
65
+ cleanup() {
66
+ [[ -n "$PROM_PF_PID" ]] && kill "$PROM_PF_PID" 2>/dev/null || true
67
+ log "removing synthetic resources (idempotent)"
68
+ # Mutation-test SMs (already deleted inline, but --ignore-not-found makes this safe)
69
+ kubectl delete servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
70
+ kubectl delete servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
71
+ # Scrape-verification resources
72
+ kubectl delete servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
73
+ kubectl delete deployment kyverno-emitter -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
74
+ kubectl delete service kyverno-emitter-svc -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
75
+ kubectl delete configmap kyverno-emitter-config -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
76
+ }
77
+ trap cleanup EXIT
78
+
79
+ # -------------------------------------------------------------------------
80
+ # Pre-flight
81
+ # -------------------------------------------------------------------------
82
+ command -v helm >/dev/null 2>&1 || fail "helm not installed"
83
+ command -v kubectl >/dev/null 2>&1 || fail "kubectl not installed"
84
+ command -v curl >/dev/null 2>&1 || fail "curl not installed"
85
+ command -v jq >/dev/null 2>&1 || fail "jq not installed"
86
+ kubectl cluster-info >/dev/null 2>&1 || fail "kubectl: no reachable cluster; set KUBECONFIG"
87
+
88
+ # kube-prom-stack must already be up — we rely on Prometheus + the
89
+ # ServiceMonitor CRD existing.
90
+ kubectl get crd servicemonitors.monitoring.coreos.com >/dev/null 2>&1 \
91
+ || fail "ServiceMonitor CRD not present — run prom-no-double-grafana.sh first"
92
+ kubectl get deployment -n "$TEST_NAMESPACE" -l "app.kubernetes.io/name=prometheus-operator" \
93
+ >/dev/null 2>&1 \
94
+ || fail "prometheus-operator not found in $TEST_NAMESPACE — run prom-no-double-grafana.sh first"
95
+
96
+ log "pre-flight checks passed"
97
+
98
+ # -------------------------------------------------------------------------
99
+ # Step 1: helm-install Kyverno
100
+ #
101
+ # Repo add is idempotent; helm upgrade --install handles fresh install + upgrade.
102
+ # `--wait` blocks until pods are Ready; admission webhook needs to be live
103
+ # before we apply the ClusterPolicy or our test ServiceMonitors.
104
+ # -------------------------------------------------------------------------
105
+ log "ensuring kyverno helm repo is configured"
106
+ helm repo add kyverno https://kyverno.github.io/kyverno/ >/dev/null 2>&1 || true
107
+ helm repo update kyverno >/dev/null 2>&1 || true
108
+
109
+ log "installing kyverno chart $KYVERNO_VERSION (waits for admission webhook Ready)"
110
+ helm upgrade --install olam-kyverno kyverno/kyverno \
111
+ --version "$KYVERNO_VERSION" \
112
+ --namespace "$KYVERNO_NAMESPACE" \
113
+ --create-namespace \
114
+ -f "$REPO_ROOT/packages/peripheral-services/helm-values/kyverno-values.yaml" \
115
+ --wait --timeout 300s 2>&1 | tail -8
116
+
117
+ # Sanity: kyverno-admission-controller Deployment Ready.
118
+ kubectl get deployment -n "$KYVERNO_NAMESPACE" -l "app.kubernetes.io/component=admission-controller" \
119
+ >/dev/null 2>&1 \
120
+ || fail "kyverno admission controller not found in $KYVERNO_NAMESPACE"
121
+
122
+ log "waiting for kyverno admission webhook to be registered with apiserver"
123
+ # The webhook registration is the LAST thing kyverno does after pod-Ready;
124
+ # poll until our ClusterPolicy can be admitted.
125
+ elapsed=0
126
+ while [ "$elapsed" -lt 120 ]; do
127
+ if kubectl get validatingwebhookconfiguration kyverno-policy-validating-webhook-cfg \
128
+ >/dev/null 2>&1; then
129
+ log "kyverno webhooks registered after ${elapsed}s"
130
+ break
131
+ fi
132
+ sleep 5
133
+ elapsed=$((elapsed + 5))
134
+ done
135
+ if [ "$elapsed" -ge 120 ]; then
136
+ fail "kyverno webhook registration timed out after 120s"
137
+ fi
138
+
139
+ # -------------------------------------------------------------------------
140
+ # Step 2: Apply the ClusterPolicy
141
+ # -------------------------------------------------------------------------
142
+ log "applying ClusterPolicy enforce-cardinality-labeldrop"
143
+ kubectl apply -f "$REPO_ROOT/packages/peripheral-services/manifests/96-kyverno-cardinality-mutate.yaml"
144
+
145
+ # Wait for policy to be Ready (Kyverno controller picks it up and reports
146
+ # readiness in status.ready / .conditions).
147
+ log "waiting up to 60s for ClusterPolicy to be Ready"
148
+ elapsed=0
149
+ while [ "$elapsed" -lt 60 ]; do
150
+ READY=$(kubectl get clusterpolicy enforce-cardinality-labeldrop \
151
+ -o jsonpath='{.status.ready}' 2>/dev/null || echo "")
152
+ if [ "$READY" = "true" ]; then
153
+ log "ClusterPolicy Ready after ${elapsed}s"
154
+ break
155
+ fi
156
+ sleep 3
157
+ elapsed=$((elapsed + 3))
158
+ done
159
+ if [ "$elapsed" -ge 60 ]; then
160
+ log "WARN: ClusterPolicy status.ready not observed within 60s; proceeding (status field can lag)"
161
+ fi
162
+
163
+ # -------------------------------------------------------------------------
164
+ # Step 3: POSITIVE test — mutation only, no backing Service
165
+ #
166
+ # Uses selector `app: kyverno-mutate-positive-test` — a label that no
167
+ # real Service carries, so this SM never competes with anything for
168
+ # Endpoints. Its sole job is to exercise the Kyverno admission webhook.
169
+ #
170
+ # Deleted immediately after assertion so the SM space is clean when
171
+ # the scrape test runs.
172
+ # -------------------------------------------------------------------------
173
+ log "POSITIVE test: applying naive ServiceMonitor (no metricRelabelings, non-Service-backed selector)"
174
+ kubectl apply -f - <<'EOF'
175
+ ---
176
+ apiVersion: monitoring.coreos.com/v1
177
+ kind: ServiceMonitor
178
+ metadata:
179
+ name: kyverno-mutate-positive-test
180
+ namespace: monitoring
181
+ labels:
182
+ release: olam-prom
183
+ spec:
184
+ namespaceSelector:
185
+ matchNames:
186
+ - monitoring
187
+ selector:
188
+ matchLabels:
189
+ app: kyverno-mutate-positive-test
190
+ endpoints:
191
+ - port: metrics
192
+ interval: 15s
193
+ # NOTE: deliberately NO metricRelabelings — Kyverno must inject it.
194
+ EOF
195
+
196
+ # Read back and assert.
197
+ ACTUAL=$(kubectl get servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" -o json \
198
+ | jq -r '.spec.endpoints[0].metricRelabelings // [] | tojson')
199
+ log "kyverno-mutate-positive-test metricRelabelings after admission: $ACTUAL"
200
+
201
+ INJECTED_COUNT=$(echo "$ACTUAL" | jq '[ .[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
202
+ if [ "$INJECTED_COUNT" -lt 1 ]; then
203
+ log "actual policy state:"
204
+ kubectl get clusterpolicy enforce-cardinality-labeldrop -o yaml >&2 || true
205
+ fail "POSITIVE test FAILED: Kyverno did not inject labeldrop into naive ServiceMonitor — third-party bypass gap NOT closed"
206
+ fi
207
+ log "PASS: naive ServiceMonitor was mutated at admission (labeldrop injected)"
208
+
209
+ log "deleting kyverno-mutate-positive-test (mutation-only test; SM space clean for scrape test)"
210
+ kubectl delete servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" --ignore-not-found=true
211
+
212
+ # -------------------------------------------------------------------------
213
+ # Step 4: IDEMPOTENCY test — mutation only, no backing Service
214
+ #
215
+ # Uses selector `app: kyverno-mutate-idempotency-test` — different from
216
+ # the positive test and from the scrape test label. No real Service.
217
+ # Deleted immediately after assertion.
218
+ # -------------------------------------------------------------------------
219
+ log "IDEMPOTENCY test: applying pre-armoured ServiceMonitor (labeldrop already present)"
220
+ kubectl apply -f - <<'EOF'
221
+ ---
222
+ apiVersion: monitoring.coreos.com/v1
223
+ kind: ServiceMonitor
224
+ metadata:
225
+ name: kyverno-mutate-idempotency-test
226
+ namespace: monitoring
227
+ labels:
228
+ release: olam-prom
229
+ spec:
230
+ namespaceSelector:
231
+ matchNames:
232
+ - monitoring
233
+ selector:
234
+ matchLabels:
235
+ app: kyverno-mutate-idempotency-test
236
+ endpoints:
237
+ - port: metrics
238
+ interval: 15s
239
+ metricRelabelings:
240
+ - action: labeldrop
241
+ regex: 'world_id|trace_id|user_id|request_id|operator_id'
242
+ EOF
243
+
244
+ DUP_COUNT=$(kubectl get servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" -o json \
245
+ | jq '[ .spec.endpoints[0].metricRelabelings[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
246
+ log "kyverno-mutate-idempotency-test labeldrop count: $DUP_COUNT"
247
+ if [ "$DUP_COUNT" -ne 1 ]; then
248
+ kubectl get servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" -o yaml >&2
249
+ fail "IDEMPOTENCY test FAILED: expected 1 labeldrop entry, got $DUP_COUNT — policy double-adds"
250
+ fi
251
+ log "PASS: pre-armoured ServiceMonitor has exactly 1 labeldrop (no double-add)"
252
+
253
+ log "deleting kyverno-mutate-idempotency-test (mutation-only test; SM space clean for scrape test)"
254
+ kubectl delete servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" --ignore-not-found=true
255
+
256
+ # -------------------------------------------------------------------------
257
+ # Step 5: SCRAPE-VERIFICATION test — dedicated SM + Service + Pod
258
+ #
259
+ # One SM (`kyverno-emitter-sm`) selects exactly one Service (`kyverno-emitter-svc`).
260
+ # No other SM in the cluster selects `app: kyverno-emitter`, so prometheus-operator
261
+ # reconciles a single clean scrape config.
262
+ #
263
+ # The SM is applied WITHOUT metricRelabelings so Kyverno's admission webhook
264
+ # fires — this is the load-bearing check that the policy applies during real
265
+ # scrape setup, not just on test fixtures.
266
+ #
267
+ # After admission we verify the spec has the labeldrop, then wait for the pod
268
+ # to be Ready and poll Prometheus for http_requests_total. We assert
269
+ # world_id is absent from all returned series.
270
+ #
271
+ # Mirrors the working pattern from dashboards-have-data.sh (single dedicated
272
+ # SM + co-located Service in `monitoring` namespace).
273
+ # -------------------------------------------------------------------------
274
+ log "SCRAPE-VERIFICATION test: deploying synthetic kyverno-emitter (emits http_requests_total{world_id})"
275
+ kubectl apply -f - <<'EOF'
276
+ ---
277
+ apiVersion: v1
278
+ kind: ConfigMap
279
+ metadata:
280
+ name: kyverno-emitter-config
281
+ namespace: monitoring
282
+ data:
283
+ metrics: |
284
+ # HELP http_requests_total Synthetic counter; world_id is the cardinality bomb
285
+ # TYPE http_requests_total counter
286
+ http_requests_total{world_id="kyverno-world",route="/api",method="GET",status_code="200"} 1
287
+ ---
288
+ apiVersion: apps/v1
289
+ kind: Deployment
290
+ metadata:
291
+ name: kyverno-emitter
292
+ namespace: monitoring
293
+ labels:
294
+ app: kyverno-emitter
295
+ spec:
296
+ replicas: 1
297
+ selector:
298
+ matchLabels:
299
+ app: kyverno-emitter
300
+ template:
301
+ metadata:
302
+ labels:
303
+ app: kyverno-emitter
304
+ spec:
305
+ containers:
306
+ - name: emitter
307
+ image: python:3.11-alpine
308
+ ports:
309
+ - containerPort: 8080
310
+ command: ["python3", "-c"]
311
+ args:
312
+ - |
313
+ import http.server
314
+ with open('/config/metrics') as f: METRICS = f.read().encode()
315
+ class H(http.server.BaseHTTPRequestHandler):
316
+ def do_GET(self):
317
+ if self.path != '/metrics':
318
+ self.send_response(404); self.end_headers(); return
319
+ self.send_response(200)
320
+ self.send_header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
321
+ self.end_headers()
322
+ self.wfile.write(METRICS)
323
+ def log_message(self, *a): pass
324
+ http.server.HTTPServer(('0.0.0.0', 8080), H).serve_forever()
325
+ volumeMounts:
326
+ - name: config
327
+ mountPath: /config
328
+ volumes:
329
+ - name: config
330
+ configMap:
331
+ name: kyverno-emitter-config
332
+ ---
333
+ apiVersion: v1
334
+ kind: Service
335
+ metadata:
336
+ name: kyverno-emitter-svc
337
+ namespace: monitoring
338
+ labels:
339
+ app: kyverno-emitter
340
+ spec:
341
+ selector:
342
+ app: kyverno-emitter
343
+ ports:
344
+ - name: metrics
345
+ port: 8080
346
+ targetPort: 8080
347
+ EOF
348
+
349
+ log "waiting for kyverno-emitter deployment Ready"
350
+ kubectl rollout status deployment/kyverno-emitter -n "$TEST_NAMESPACE" --timeout=120s
351
+
352
+ # Apply the dedicated ServiceMonitor WITHOUT metricRelabelings so Kyverno
353
+ # mutates it at admission — this proves the policy fires on real SM objects,
354
+ # not just on the POSITIVE test fixture.
355
+ log "applying kyverno-emitter-sm (no metricRelabelings — Kyverno must inject)"
356
+ kubectl apply -f - <<'EOF'
357
+ ---
358
+ apiVersion: monitoring.coreos.com/v1
359
+ kind: ServiceMonitor
360
+ metadata:
361
+ name: kyverno-emitter-sm
362
+ namespace: monitoring
363
+ labels:
364
+ release: olam-prom
365
+ spec:
366
+ namespaceSelector:
367
+ matchNames:
368
+ - monitoring
369
+ selector:
370
+ matchLabels:
371
+ app: kyverno-emitter
372
+ endpoints:
373
+ - port: metrics
374
+ interval: 15s
375
+ # NOTE: NO metricRelabelings — Kyverno must inject the labeldrop at admission.
376
+ EOF
377
+
378
+ # Verify Kyverno mutated this SM too (belt-and-suspenders: proves the policy
379
+ # applies to the SM that actually drives the scrape, not just the test fixtures).
380
+ SCRAPE_SM_ACTUAL=$(kubectl get servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" -o json \
381
+ | jq -r '.spec.endpoints[0].metricRelabelings // [] | tojson')
382
+ log "kyverno-emitter-sm metricRelabelings after admission: $SCRAPE_SM_ACTUAL"
383
+
384
+ SCRAPE_SM_INJECTED=$(echo "$SCRAPE_SM_ACTUAL" | jq '[ .[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
385
+ if [ "$SCRAPE_SM_INJECTED" -lt 1 ]; then
386
+ log "actual policy state:"
387
+ kubectl get clusterpolicy enforce-cardinality-labeldrop -o yaml >&2 || true
388
+ fail "SCRAPE-VERIFICATION test FAILED: Kyverno did not mutate kyverno-emitter-sm at admission"
389
+ fi
390
+ log "PASS: kyverno-emitter-sm was mutated at admission (labeldrop injected)"
391
+
392
+ # Port-forward Prometheus and poll for metric samples.
393
+ log "port-forwarding svc/prometheus-operated $PROM_LOCAL_PORT:9090"
394
+ kubectl port-forward \
395
+ -n "$TEST_NAMESPACE" \
396
+ "svc/prometheus-operated" \
397
+ "${PROM_LOCAL_PORT}:9090" &
398
+ PROM_PF_PID=$!
399
+ sleep "$PF_BIND_SECONDS"
400
+ kill -0 "$PROM_PF_PID" 2>/dev/null \
401
+ || fail "Prometheus port-forward exited prematurely"
402
+
403
+ PROM_URL="http://localhost:${PROM_LOCAL_PORT}"
404
+
405
+ # Direct-metric polling rather than target-discovery polling.
406
+ #
407
+ # Rationale: kube-prometheus-stack's default relabel sets the `job` label
408
+ # from the k8s Service name. Polling by job-name is brittle — operator
409
+ # reconciliation races, dropped-target filtering, and rare CRD revision
410
+ # lag have all surfaced as "target not in activeTargets" flakes during
411
+ # earlier ingress-integration runs. What we ACTUALLY care about is
412
+ # whether the mutated relabel was applied to a real scrape sample. So
413
+ # poll for the metric directly. With a single SM selecting on
414
+ # `app=kyverno-emitter`, any http_requests_total series returned
415
+ # necessarily came through kyverno-emitter-sm.
416
+ log "polling Prometheus for http_requests_total samples (up to ${TARGET_DISCOVERY_TIMEOUT}s)"
417
+ elapsed=0
418
+ RESULT=""
419
+ while [ "$elapsed" -lt "$TARGET_DISCOVERY_TIMEOUT" ]; do
420
+ RESULT=$(curl -sf "${PROM_URL}/api/v1/query?query=http_requests_total" 2>/dev/null || echo "")
421
+ if [ -n "$RESULT" ]; then
422
+ SERIES_COUNT=$(echo "$RESULT" | jq '.data.result | length' 2>/dev/null || echo "0")
423
+ if [ "$SERIES_COUNT" -ge 1 ]; then
424
+ log "http_requests_total returned $SERIES_COUNT series after ${elapsed}s"
425
+ break
426
+ fi
427
+ fi
428
+ sleep "$SCRAPE_POLL_INTERVAL"
429
+ elapsed=$((elapsed + SCRAPE_POLL_INTERVAL))
430
+ done
431
+
432
+ if [ "$elapsed" -ge "$TARGET_DISCOVERY_TIMEOUT" ]; then
433
+ log "Active targets snapshot for diagnosis:"
434
+ curl -sf "${PROM_URL}/api/v1/targets" | jq '.data.activeTargets[] | {job: .labels.job, service: .labels.service, namespace: .labels.namespace, health: .health, lastError: .lastError}' >&2 || true
435
+ log "ServiceMonitor kyverno-emitter-sm status:"
436
+ kubectl get servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" -o yaml >&2 || true
437
+ log "prometheus-operator log tail (last 50 lines):"
438
+ kubectl logs -n "$TEST_NAMESPACE" -l "app.kubernetes.io/name=prometheus-operator" --tail=50 >&2 || true
439
+ fail "Prometheus did not scrape kyverno-emitter within ${TARGET_DISCOVERY_TIMEOUT}s"
440
+ fi
441
+
442
+ SERIES_COUNT=$(echo "$RESULT" | jq '.data.result | length')
443
+
444
+ LEAKED=$(echo "$RESULT" | jq '[.data.result[] | .metric | has("world_id")] | any')
445
+ if [ "$LEAKED" = "true" ]; then
446
+ echo "$RESULT" | jq '.data.result[] | .metric' >&2
447
+ fail "world_id label leaked into Prometheus — Kyverno-mutated relabel did NOT take effect at scrape time"
448
+ fi
449
+
450
+ log "PASS: kyverno-emitter scraped via kyverno-emitter-sm; world_id absent at scrape time"
451
+ log "PASS: C8 verified — Kyverno mutates third-party-shaped ServiceMonitors at admission and the mutation takes effect at scrape time"
452
+ exit 0