Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added (decision dashboard)
- Decision, receipt and chain pages in `agentctl-web`: `/decisions`, `/decisions/{seq}` and `/receipts`. They read a receipt export from `--export-dir` (offline, no cluster) or from the receipt-writer, and verify it in process. The badge says Verified only under a pinned `--trust-root`. See `docs/dashboard.md`.
- `make demo-dashboard` serves a sample export offline. `tools/demo-export` writes it.
- The chart wires the web UI to the receipt-writer when both are enabled, and admits the web pods through the writer NetworkPolicy.

Decision architecture series. The direct action path now decides in layers:
invariants, policy packs, a threshold evaluator, an escalate-only decision
model in shadow, and human approval. Each decision can get a signed receipt
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ SETUP_ENVTEST := $(LOCALBIN)/setup-envtest
STATICCHECK := $(LOCALBIN)/staticcheck
CONTROLLER_GEN := $(LOCALBIN)/controller-gen

.PHONY: help test validate test-unit test-go test-anf-snapshot vet-anf-snapshot verify-anf-snapshot test-python setup-envtest test-controller fmt fmt-anf-snapshot fmt-check fmt-check-anf-snapshot vet lint staticcheck-anf-snapshot build build-agentctl build-agentctl-web build-receipt-writer build-anf-snapshot install-agentctl scan-secrets clean-venv check-python-version helm-lint test-cluster test-smoke test-e2e-cluster manifests generate decision-train
.PHONY: help test validate test-unit test-go test-anf-snapshot vet-anf-snapshot verify-anf-snapshot test-python setup-envtest test-controller fmt fmt-anf-snapshot fmt-check fmt-check-anf-snapshot vet lint staticcheck-anf-snapshot build build-agentctl build-agentctl-web build-receipt-writer demo-dashboard build-anf-snapshot install-agentctl scan-secrets clean-venv check-python-version helm-lint test-cluster test-smoke test-e2e-cluster manifests generate decision-train

.DEFAULT_GOAL := help

Expand Down Expand Up @@ -177,6 +177,11 @@ build:
@echo "Building Go binaries..."
@$(GO) build ./...

demo-dashboard: ## Serve the decision dashboard offline from a sample export (127.0.0.1:8090)
@$(GO) run ./tools/demo-export -out bin/demo-dashboard
@echo "Serving http://127.0.0.1:8090 (Ctrl-C to stop)"
@$(GO) run ./cmd/agentctl-web --export-dir bin/demo-dashboard/export --trust-root bin/demo-dashboard/pinned-trust.json

decision-train: ## Seed DRAFT synthetic data and train the decision model twice; fails unless both artifacts match
@bash tools/decision-train/run.sh

Expand Down
10 changes: 9 additions & 1 deletion charts/templates/receipt-writer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,22 @@ spec:
policyTypes:
- Ingress
- Egress
# Only operator pods may call the writer. The writer needs no egress.
# Only operator pods, and the web UI when it is on, may call the writer. The
# writer needs no egress.
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: agentic-operator
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: operator
{{- if .Values.webUI.enabled }}
- podSelector:
matchLabels:
app.kubernetes.io/name: agentic-operator
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: webui
{{- end }}
ports:
- port: {{ $port }}
protocol: TCP
Expand Down
43 changes: 43 additions & 0 deletions charts/templates/webui.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{{- if .Values.webUI.enabled }}
{{- /* The writer token Secret and trust ConfigMap live in the release namespace.
Pods can only mount them from the same namespace. */}}
{{- if and (.Values.global.receipts).enabled (ne (include "agentic-operator.namespace" .) .Release.Namespace) }}
{{- fail "webUI with global.receipts.enabled needs global.namespace to match the release namespace, so the web pods can mount the writer token and trust root" }}
{{- end }}
---
apiVersion: v1
kind: ServiceAccount
Expand Down Expand Up @@ -98,6 +103,16 @@ spec:
protocol: TCP
args:
- "--addr=:8090"
{{- with .Values.global.receipts }}
{{- if .enabled }}
# Decision, receipt and chain pages read the receipt-writer.
- {{ printf "--writer-url=http://%s.%s.svc:%d" (printf "%s-receipt-writer" $.Release.Name | trunc 63 | trimSuffix "-") $.Release.Namespace (int (.port | default 8080)) | quote }}
- {{ printf "--writer-token-file=/etc/receipts/token/%s" ((.token).key | default "token") | quote }}
{{- if (.trustRoot).existingConfigMap }}
- {{ printf "--trust-root=/etc/receipts/trust/%s" ((.trustRoot).key | default "trust.json") | quote }}
{{- end }}
{{- end }}
{{- end }}
livenessProbe:
httpGet:
path: /healthz
Expand All @@ -110,13 +125,41 @@ spec:
port: http
initialDelaySeconds: 3
periodSeconds: 10
{{- if (.Values.global.receipts).enabled }}
volumeMounts:
- name: receipts-token
mountPath: /etc/receipts/token
readOnly: true
{{- if ((.Values.global.receipts).trustRoot).existingConfigMap }}
- name: receipts-trust
mountPath: /etc/receipts/trust
readOnly: true
{{- end }}
{{- end }}
resources:
{{- toYaml .Values.webUI.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
{{- with .Values.global.receipts }}
{{- if .enabled }}
volumes:
- name: receipts-token
secret:
secretName: {{ (.token).existingSecret | default (printf "%s-receipt-writer-token" $.Release.Name | trunc 63 | trimSuffix "-") }}
defaultMode: 0444
Comment thread
shreyanshjain7174 marked this conversation as resolved.
{{- if (.trustRoot).existingConfigMap }}
- name: receipts-trust
configMap:
name: {{ (.trustRoot).existingConfigMap }}
items:
- key: {{ (.trustRoot).key | default "trust.json" }}
path: {{ (.trustRoot).key | default "trust.json" }}
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: v1
kind: Service
Expand Down
164 changes: 164 additions & 0 deletions charts/tests/webui_decisions_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# helm-unittest spec for the web UI decision pages (docs/architecture/demo-dashboard.md).
# Run with: helm unittest charts
suite: web ui decision pages
templates:
- webui.yaml
- receipt-writer.yaml
tests:
- it: gives the web UI no writer flags when receipts are off
set:
license:
key: "test"
webUI:
enabled: true
asserts:
- notContains:
path: spec.template.spec.containers[0].args
content: "--writer-token-file=/etc/receipts/token/token"
documentIndex: 3
template: webui.yaml
- notExists:
path: spec.template.spec.volumes
documentIndex: 3
template: webui.yaml

- it: points the web UI at the writer with a pinned trust root
release:
name: rel
namespace: agentic-system
set:
license:
key: "test"
webUI:
enabled: true
global:
receipts:
enabled: true
signingKey:
existingSecret: receipt-signing-key
trustRoot:
existingConfigMap: receipt-trust-root
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: "--writer-url=http://rel-receipt-writer.agentic-system.svc:8080"
documentIndex: 3
template: webui.yaml
- contains:
path: spec.template.spec.containers[0].args
content: "--writer-token-file=/etc/receipts/token/token"
documentIndex: 3
template: webui.yaml
- contains:
path: spec.template.spec.containers[0].args
content: "--trust-root=/etc/receipts/trust/trust.json"
documentIndex: 3
template: webui.yaml
- contains:
path: spec.template.spec.volumes
content:
name: receipts-token
secret:
secretName: rel-receipt-writer-token
defaultMode: 0444
documentIndex: 3
template: webui.yaml
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: receipts-trust
mountPath: /etc/receipts/trust
readOnly: true
documentIndex: 3
template: webui.yaml

- it: omits the trust flag when no trust root is pinned
release:
name: rel
namespace: agentic-system
set:
license:
key: "test"
webUI:
enabled: true
global:
receipts:
enabled: true
insecureNoPin: true
signingKey:
existingSecret: receipt-signing-key
asserts:
- notContains:
path: spec.template.spec.containers[0].args
content: "--trust-root=/etc/receipts/trust/trust.json"
documentIndex: 3
template: webui.yaml

- it: lets the web UI reach the writer only when it is enabled
release:
name: rel
namespace: agentic-system
set:
license:
key: "test"
webUI:
enabled: true
global:
receipts:
enabled: true
signingKey:
existingSecret: receipt-signing-key
trustRoot:
existingConfigMap: receipt-trust-root
asserts:
- lengthEqual:
path: spec.ingress[0].from
count: 2
documentIndex: 3
template: receipt-writer.yaml
- equal:
path: spec.ingress[0].from[1].podSelector.matchLabels["app.kubernetes.io/component"]
value: webui
documentIndex: 3
template: receipt-writer.yaml

- it: keeps the writer ingress to operator pods when the web UI is off
release:
name: rel
namespace: agentic-system
set:
license:
key: "test"
global:
receipts:
enabled: true
signingKey:
existingSecret: receipt-signing-key
trustRoot:
existingConfigMap: receipt-trust-root
asserts:
- lengthEqual:
path: spec.ingress[0].from
count: 1
documentIndex: 3
template: receipt-writer.yaml

- it: fails when the web namespace differs from the writer namespace
release:
name: rel
namespace: other-ns
set:
license:
key: "test"
webUI:
enabled: true
global:
namespace: agentic-system
receipts:
enabled: true
signingKey:
existingSecret: receipt-signing-key
template: webui.yaml
asserts:
- failedTemplate:
errorPattern: "needs global.namespace to match the release namespace"
Binary file added cmd/agentctl-web/agentctl-web
Binary file not shown.
Loading
Loading