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
6 changes: 6 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
context: ./authbridge
dockerfile: cmd/authbridge-lite/Dockerfile

# SPARC reflection service — the backend the `sparc` plugin calls.
# Deployed once per cluster via authbridge/sparc-service/deploy.
- name: sparc-service
context: ./authbridge/sparc-service
dockerfile: Dockerfile

steps:
# 1. Checkout code
- name: Checkout repository
Expand Down Expand Up @@ -98,7 +104,7 @@
# Always use the computed tag
type=raw,value=${{ steps.tag.outputs.tag }}
# Add 'latest' tag for version tags, workflow_dispatch, and pushes to main
type=raw,value=latest,enable=${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' }}

Check warning on line 107 in .github/workflows/build.yaml

View workflow job for this annotation

GitHub Actions / YAML Lint

107:151 [line-length] line too long (189 > 150 characters)

# 7. Build and push image
- name: Build and push ${{ matrix.image_config.name }}
Expand Down
39 changes: 13 additions & 26 deletions authbridge/demos/finance-sparc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# export WX_API_KEY and WX_PROJECT_ID (e.g. `set -a; . /path/.env; set +a`).

.PHONY: help preflight host-setup build-images build-authbridge load-images \
ollama-up sparc-config deploy wait-pods patch-config setup-keycloak drive demo \
ollama-up deploy wait-pods patch-config setup-keycloak drive demo \
build-abctl show-result undeploy logs-agent logs-sparc logs-ollama

.DEFAULT_GOAL := help
Expand All @@ -36,6 +36,8 @@ AUTHBRIDGE_IMAGE ?= ghcr.io/kagenti/kagenti-extensions/authbridge:v0.6.0-alpha.
AGENT_IMAGE := finance-agent:latest
MCP_IMAGE := finance-mcp:latest
SPARC_IMAGE := sparc-service:latest
# Shared SPARC-service installer (the demo deploys the service through it).
SPARC_DEPLOY := ../../sparc-service/deploy
# Pinned (must match k8s/ollama.yaml). Override only if you stage a different tag.
OLLAMA_IMAGE := ollama/ollama:0.30.2
# In-cluster Ollama (the agent's reasoning LLM for the watsonx path).
Expand Down Expand Up @@ -128,28 +130,15 @@ load-images: build-images build-authbridge ## Load all images into kind
$(CONTAINER_RUNTIME) exec $(KIND_NODE) ctr -n k8s.io images tag localhost/$$img docker.io/library/$$img >/dev/null 2>&1 || true; \
done

sparc-config: ## Create the SPARC service ConfigMap (+ watsonx secret) for $(PROVIDER)
@if [ "$(PROVIDER)" = "watsonx" ]; then \
kubectl -n $(PLATFORM_NS) create configmap sparc-service-config \
--from-literal=SPARC_LLM_PROVIDER=watsonx --from-literal=SPARC_MODEL=$(SPARC_MODEL) \
--from-literal=SPARC_TRACK=fast_track --from-literal=PORT=8090 \
--dry-run=client -o yaml | kubectl apply -f - ; \
kubectl -n $(PLATFORM_NS) create secret generic sparc-watsonx \
--from-literal=WX_API_KEY="$${WX_API_KEY}" --from-literal=WX_PROJECT_ID="$${WX_PROJECT_ID}" \
--from-literal=WX_URL="$${WX_URL:-https://us-south.ml.cloud.ibm.com}" \
--dry-run=client -o yaml | kubectl apply -f - ; \
else \
kubectl -n $(PLATFORM_NS) create configmap sparc-service-config \
--from-literal=SPARC_LLM_PROVIDER=ollama --from-literal=SPARC_MODEL=$(SPARC_MODEL) \
--from-literal=SPARC_TRACK=fast_track --from-literal=PORT=8090 \
--from-literal=OLLAMA_BASE_URL=$(SPARC_OLLAMA_URL) \
--dry-run=client -o yaml | kubectl apply -f - ; \
kubectl -n $(PLATFORM_NS) create secret generic sparc-watsonx --dry-run=client -o yaml | kubectl apply -f - ; \
fi
@kubectl -n $(PLATFORM_NS) rollout restart deploy/sparc-service >/dev/null 2>&1 || true

deploy: sparc-config ## Deploy the SPARC service, finance MCP server, and agent
kubectl apply -f k8s/sparc-service.yaml
deploy: ## Deploy the SPARC service (shared installer), finance MCP server, and agent
@# The SPARC service is deployed through the shared installer — the same
@# one-command path a user runs for any cluster. The demo's images are
@# already built + kind-loaded by load-images, so we pass IMAGE and skip the
@# installer's own build. WX_* creds (watsonx) are read from the environment.
$(MAKE) -C $(SPARC_DEPLOY) install \
NAMESPACE=$(PLATFORM_NS) IMAGE=$(SPARC_IMAGE) PROVIDER=$(PROVIDER) \
MODEL=$(SPARC_MODEL) TRACK=fast_track OLLAMA_BASE_URL=$(SPARC_OLLAMA_URL) \
KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME)
kubectl apply -f k8s/finance-mcp.yaml
kubectl apply -f k8s/agent.yaml
@# Point the agent's reasoning LLM at the provider's Ollama (in-cluster for
Expand Down Expand Up @@ -195,9 +184,7 @@ undeploy: ## Remove demo resources (kagenti install untouched)
-kubectl -n $(NS) delete -f k8s/agent.yaml --ignore-not-found
-kubectl -n $(NS) delete -f k8s/finance-mcp.yaml --ignore-not-found
-kubectl -n $(NS) delete -f k8s/ollama.yaml --ignore-not-found
-kubectl delete -f k8s/sparc-service.yaml --ignore-not-found
-kubectl -n $(PLATFORM_NS) delete configmap sparc-service-config --ignore-not-found
-kubectl -n $(PLATFORM_NS) delete secret sparc-watsonx --ignore-not-found
-$(MAKE) -C $(SPARC_DEPLOY) uninstall NAMESPACE=$(PLATFORM_NS)
-kubectl -n $(NS) delete configmap authbridge-config-$(AGENT) --ignore-not-found
-kubectl -n $(NS) delete secret -l kagenti.io/client-name=$(AGENT) --ignore-not-found
@echo "[*] demo resources removed."
Expand Down
14 changes: 10 additions & 4 deletions authbridge/demos/finance-sparc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ make demo PROVIDER=ollama

`make demo` is one-shot and idempotent. It: checks cluster egress (a no-op on healthy
networks), builds + loads images (including an authbridge image carrying the `sparc` plugin),
stages + warms the in-cluster Ollama model(s), deploys the SPARC service + finance MCP server +
agent, enables the pipeline, configures Keycloak, and drives the two-turn scenario **through
real inbound auth** (no jwt bypass), printing SPARC's verdicts.
stages + warms the in-cluster Ollama model(s), deploys the SPARC service via the shared
[installer](../../sparc-service/deploy/README.md), deploys the finance MCP server + agent,
enables the pipeline, configures Keycloak, and drives the two-turn scenario **through real
inbound auth** (no jwt bypass), printing SPARC's verdicts.

> The SPARC service is the one piece you also need outside this demo. The demo deploys it for
> you through the same one-command installer a user would run for any agent —
> [`sparc-service/deploy`](../../sparc-service/deploy/README.md) — so there's a single, reusable
> path. It must be deployed before the `sparc` plugin is enabled.

## How to read the output

Expand Down Expand Up @@ -115,7 +121,7 @@ real inbound auth** (no jwt bypass), printing SPARC's verdicts.
| `finance-agent/` | A regular A2A agent: Ollama reasoning, MCP `tools/list` discovery, MCP tool execution. |
| `finance-mcp/` | MCP server: `get_transaction`, `lookup_customer`, `issue_refund`, `get_invoice`, `list_currencies` over a small multi-record dataset (real id: `TX4827`). |
| `k8s/sparc-patch.yaml` | Pipeline additions: `a2a-parser` inbound; `inference-parser` + `mcp-parser` + `sparc` outbound (mcp mode, fast_track, reflect, `skip_tools: [list_currencies]`). |
| `k8s/{agent,finance-mcp,sparc-service}.yaml` | Workload manifests. |
| `k8s/{agent,finance-mcp}.yaml` | Demo workload manifests. (The SPARC service itself is deployed via the shared installer at [`sparc-service/deploy/`](../../sparc-service/deploy/README.md).) |
| `k8s/ollama.yaml` | In-cluster Ollama for the agent's reasoning model on the **watsonx** path; model staged onto the node — no host networking. (The `PROVIDER=ollama` path uses your host Ollama instead.) |
| `scripts/host-setup.sh` | Generic egress check (no-op on healthy networks; portable MSS clamp only if the link blackholes PMTUD). |
| `scripts/stage-ollama-model.sh` | Copies a model from the host Ollama store onto the kind node (local, no pull). |
Expand Down
3 changes: 1 addition & 2 deletions authbridge/demos/finance-sparc/scripts/drive-demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ send 2 'The transaction id is TX4827. Please proceed with the refund, reason: du
echo " agent> $(reply /tmp/finance-t2.json)"

say "SPARC verdicts recorded for this session:"
SID="$CTX"
kubectl -n "$NS" port-forward "deploy/$AGENT" 19094:9094 >/tmp/finance-pf94.log 2>&1 &
PF94=$!; trap 'kill $PF $PF94 $KCPF 2>/dev/null || true' EXIT
sleep 3
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
curl -s -m 10 "http://localhost:19094/v1/sessions/$SID" 2>/dev/null | python3 "$SCRIPT_DIR/show-verdicts.py"
python3 "$SCRIPT_DIR/show-verdicts.py" "http://localhost:19094"
echo
echo "Done. Inspect the full pipeline with: make show-result"
49 changes: 38 additions & 11 deletions authbridge/demos/finance-sparc/scripts/show-verdicts.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,53 @@
#!/usr/bin/env python3
"""Print the SPARC plugin verdicts from a session snapshot (read on stdin)."""
"""Print the SPARC plugin verdicts recorded by an agent's authbridge session API.

Scans every session (an agent's outbound calls can land in the conversation
session or the `default` bucket depending on timing), de-duplicates, and prints
each SPARC verdict. Pass the session-API base URL as the first argument
(default http://localhost:19094).
"""
import json
import sys
import urllib.request

BASE = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:19094"


def get(path):
with urllib.request.urlopen(BASE + path, timeout=10) as resp:
return json.load(resp)
Comment on lines +13 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add Python 3.12 type hints for script interfaces and state

authbridge/demos/finance-sparc/scripts/show-verdicts.py (under authbridge/**/*.py) has untyped top-level state and function/interface code (BASE, get, and likely seen/rows at 27-28), violating the repo’s Python 3.12 typing rule. Add explicit type hints using modern union syntax (e.g., str | None).

🧰 Tools
🪛 Ruff (0.15.15)

[error] 17-17: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/demos/finance-sparc/scripts/show-verdicts.py` around lines 13 -
18, Add Python 3.12 type annotations for the module-level state and function
signature: annotate BASE as str | None (or str if always a string), add a return
type and parameter types to get (e.g., def get(path: str) -> Any or dict[str,
Any] as appropriate), and annotate the top-level containers seen and rows (e.g.,
set[str], list[dict[str, Any]]). Use modern union syntax (A | B) where needed
and import typing-only symbols (Any, dict) if required.



try:
data = json.load(sys.stdin)
sessions = get("/v1/sessions").get("sessions", [])
except Exception:
print(" (no session events yet)")
print(" (could not reach the session API)")
sys.exit(0)

seen = False
for event in data.get("events", []):
for inv in (event.get("invocations") or {}).get("outbound", []):
if inv.get("plugin") == "sparc" and inv.get("action") in ("allow", "modify", "deny", "observe"):
seen = set()
rows = []
for s in sessions:
try:
snapshot = get("/v1/sessions/" + s["id"])
except Exception:
continue
for event in snapshot.get("events", []):
for inv in (event.get("invocations") or {}).get("outbound", []):
if inv.get("plugin") != "sparc":
continue
if inv.get("action") not in ("allow", "modify", "deny", "observe"):
continue
det = inv.get("details", {})
seen = True
score = det.get("score")
key = (inv.get("action"), inv.get("reason"), det.get("tool"), score)
if key in seen:
continue
seen.add(key)
score_s = f" score={score}" if score not in (None, "") else ""
print(
rows.append(
" SPARC {}/{} tool={}{}".format(
inv.get("action"), inv.get("reason"), det.get("tool"), score_s
)
)
if not seen:
print(" (no sparc verdicts found in session; check `make logs-sparc`)")

print("\n".join(rows) if rows else " (no sparc verdicts found in any session; check `make logs-sparc`)")
16 changes: 16 additions & 0 deletions authbridge/docs/sparc-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ grounded) is `<=` it, any reject is escalated to `deny`.
> governs the case where the conversation/tool context is missing (no `inference-parser` event):
> `open` skips and records `no_inference_context`; `closed` blocks.

## Prerequisite: deploy the SPARC service

The plugin is a thin client — it calls the [SPARC reflection service](../sparc-service/README.md)
over HTTP. **Deploy that service once per cluster before enabling this plugin**, or every tool call
hits an unreachable reflector (and falls back to `fail_policy`). It's one command:

```bash
cd authbridge/sparc-service/deploy
export WX_API_KEY=... WX_PROJECT_ID=... # or PROVIDER=ollama, openai, ...
make install # deploys into kagenti-system by default
```

See [`sparc-service/deploy/README.md`](../sparc-service/deploy/README.md) for providers, the
local-image path (`make image install` on kind), and configuration. Point `reflector_endpoint`
below at the resulting service (`http://sparc-service.<namespace>.svc:8090`).

## Configuration

```yaml
Expand Down
15 changes: 15 additions & 0 deletions authbridge/sparc-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ This service is deliberately *just* a reflection wrapper:
- It returns SPARC's verdict **faithfully**. All enforcement policy (observe / inject /
deny, score thresholds) lives in the AuthBridge plugin, not here.

## Deploy to a cluster (one command)

Deploy this service **once per cluster, before enabling the `sparc` plugin** on any agent:

```bash
cd deploy
export WX_API_KEY=... WX_PROJECT_ID=... # or PROVIDER=ollama, openai, ...
make install # → kagenti-system; published image
# kind dev cluster (build + load a local image first):
make image install
```

See [`deploy/README.md`](deploy/README.md) for all providers, namespaces, and the local-build path.
The rest of this document covers the service's HTTP API and environment configuration.

## API

### `POST /reflect`
Expand Down
90 changes: 90 additions & 0 deletions authbridge/sparc-service/deploy/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Install the SPARC reflection service into a cluster — one command.
#
# Deploy this ONCE per cluster BEFORE enabling the `sparc` plugin on any agent.
#
# make install # watsonx (needs WX_API_KEY / WX_PROJECT_ID)
# make install PROVIDER=ollama OLLAMA_BASE_URL=http://host.docker.internal:11434
# make install PROVIDER=openai # needs OPENAI_API_KEY
# make image install # build the image locally + load into kind first
# make uninstall
#
# By default it deploys the published image. On a kind dev cluster (or to run a
# local build) prepend the `image` target to build + load sparc-service:latest.

.PHONY: help install uninstall image status

.DEFAULT_GOAL := help

PROVIDER ?= watsonx
NAMESPACE ?= kagenti-system
IMAGE ?= ghcr.io/kagenti/kagenti-extensions/sparc-service:latest
TRACK ?= fast_track
LLM_TIMEOUT ?= 120
PORT ?= 8090
# Optional (provider-dependent). MODEL defaults per-provider in the service.
MODEL ?=
OLLAMA_BASE_URL ?= http://host.docker.internal:11434
LLM_KWARGS_JSON ?=
LLM_REGISTRY_ID ?=
# Local build / kind (only used by `make image`).
KIND_CLUSTER_NAME ?= kagenti
KIND_NODE := $(KIND_CLUSTER_NAME)-control-plane
CONTAINER_RUNTIME ?= $(shell docker info >/dev/null 2>&1 && echo docker || (podman info >/dev/null 2>&1 && echo podman) || echo docker)

# Provider credential env vars copied into the sparc-creds Secret when present.
# LiteLLM (openai/azure/litellm) reads its provider key from the environment, so
# injecting these as env via the Secret is enough.
CRED_VARS := WX_API_KEY WX_PROJECT_ID WX_URL OPENAI_API_KEY OPENAI_BASE_URL \
AZURE_API_KEY ANTHROPIC_API_KEY GEMINI_API_KEY OLLAMA_API_KEY

help: ## Show this menu
@printf "\nInstall the SPARC reflection service (deploy before enabling the sparc plugin).\n\n"
@printf "Run:\n \033[1mmake install\033[0m (watsonx; needs WX_* env)\n"
@printf " \033[1mmake install PROVIDER=ollama\033[0m (local ollama)\n"
@printf " \033[1mmake image install\033[0m (build + kind-load, then install)\n\n"
@printf "Targets:\n"
@awk 'BEGIN{FS=":.*## "} /^[a-zA-Z_-]+:.*## /{printf " \033[36m%-12s\033[0m %s\n",$$1,$$2}' $(MAKEFILE_LIST)
@printf "\nVars: PROVIDER(=%s) NAMESPACE(=%s) IMAGE MODEL TRACK OLLAMA_BASE_URL\n" "$(PROVIDER)" "$(NAMESPACE)"
@printf " LLM_KWARGS_JSON LLM_REGISTRY_ID KIND_CLUSTER_NAME\n\n"

image: ## Build the sparc-service image locally and load it into kind

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ctr ... tag localhost/$(IMAGE) docker.io/library/$(IMAGE) produces a doubled-registry ref (docker.io/library/ghcr.io/...) since IMAGE is a full ghcr path by default. It's || true defensive and mirrors the demo's existing pattern, so it won't break — just noting it likely no-ops for the ghcr-tagged default image.

@echo "[*] building $(IMAGE)"
$(CONTAINER_RUNTIME) build -t $(IMAGE) ..
@echo "[*] kind load $(IMAGE) into $(KIND_CLUSTER_NAME)"
kind load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME)
@# Let containerd resolve the bare docker.io/library/<img> ref kubelet uses.
-$(CONTAINER_RUNTIME) exec $(KIND_NODE) ctr -n k8s.io images tag localhost/$(IMAGE) docker.io/library/$(IMAGE) >/dev/null 2>&1 || true

install: ## Create config + creds and deploy the SPARC service into $(NAMESPACE)
@kubectl get ns $(NAMESPACE) >/dev/null 2>&1 || kubectl create ns $(NAMESPACE)
@echo "[*] config: provider=$(PROVIDER) namespace=$(NAMESPACE) track=$(TRACK)"
@# --- ConfigMap (non-secret settings) ---
@cfg="--from-literal=SPARC_LLM_PROVIDER=$(PROVIDER) --from-literal=SPARC_TRACK=$(TRACK)"; \
cfg="$$cfg --from-literal=SPARC_LLM_TIMEOUT=$(LLM_TIMEOUT) --from-literal=PORT=$(PORT)"; \
[ -n "$(MODEL)" ] && cfg="$$cfg --from-literal=SPARC_MODEL=$(MODEL)"; \
[ "$(PROVIDER)" = "ollama" ] && cfg="$$cfg --from-literal=OLLAMA_BASE_URL=$(OLLAMA_BASE_URL)"; \
[ -n "$(LLM_KWARGS_JSON)" ] && cfg="$$cfg --from-literal=SPARC_LLM_KWARGS_JSON=$(LLM_KWARGS_JSON)"; \
[ -n "$(LLM_REGISTRY_ID)" ] && cfg="$$cfg --from-literal=SPARC_LLM_REGISTRY_ID=$(LLM_REGISTRY_ID)"; \
kubectl -n $(NAMESPACE) create configmap sparc-service-config $$cfg --dry-run=client -o yaml | kubectl apply -f -
@# --- Secret (provider credentials present in the environment) ---
@sec=""; for v in $(CRED_VARS); do \
val=$$(printenv $$v || true); [ -n "$$val" ] && sec="$$sec --from-literal=$$v=$$val"; \
done; \
kubectl -n $(NAMESPACE) create secret generic sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -
Comment on lines +70 to +73

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid creating an empty Secret in install.

When none of $(CRED_VARS) is set (for example PROVIDER=ollama), kubectl create secret generic sparc-creds $$sec ... runs with no --from-* flags and fails, which breaks the intended credential-less install path.

🔧 Suggested fix
 	`@sec`=""; for v in $(CRED_VARS); do \
 	  val=$$(printenv $$v || true); [ -n "$$val" ] && sec="$$sec --from-literal=$$v=$$val"; \
 	done; \
-	kubectl -n $(NAMESPACE) create secret generic sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -
+	if [ -n "$$sec" ]; then \
+	  kubectl -n $(NAMESPACE) create secret generic sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -; \
+	else \
+	  kubectl -n $(NAMESPACE) delete secret sparc-creds --ignore-not-found >/dev/null; \
+	fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@sec=""; for v in $(CRED_VARS); do \
val=$$(printenv $$v || true); [ -n "$$val" ] && sec="$$sec --from-literal=$$v=$$val"; \
done; \
kubectl -n $(NAMESPACE) create secret generic sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -
`@sec`=""; for v in $(CRED_VARS); do \
val=$$(printenv $$v || true); [ -n "$$val" ] && sec="$$sec --from-literal=$$v=$$val"; \
done; \
if [ -n "$$sec" ]; then \
kubectl -n $(NAMESPACE) create secret generic sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -; \
else \
kubectl -n $(NAMESPACE) delete secret sparc-creds --ignore-not-found >/dev/null; \
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/sparc-service/deploy/Makefile` around lines 70 - 73, The Makefile
currently builds a sec string from CRED_VARS and always runs the kubectl create
secret command even when sec is empty, which causes failure for credential-less
installs; change the install logic to check if the computed sec variable is
non-empty before invoking "kubectl -n $(NAMESPACE) create secret generic
sparc-creds $$sec --dry-run=client -o yaml | kubectl apply -f -", e.g. after the
loop test "[ -n \"$$sec\" ] && (kubectl ... ) || echo 'no credentials; skipping
secret creation'", ensuring you reference the same sec and CRED_VARS variables
used in the snippet so the secret command is only executed when at least one
--from-literal was appended.

@# --- Workload ---
kubectl -n $(NAMESPACE) apply -f sparc-service.yaml
kubectl -n $(NAMESPACE) set image deploy/sparc-service sparc-service=$(IMAGE) >/dev/null
@kubectl -n $(NAMESPACE) rollout restart deploy/sparc-service >/dev/null 2>&1 || true
kubectl -n $(NAMESPACE) rollout status deploy/sparc-service --timeout=180s
@echo "[*] SPARC service ready at http://sparc-service.$(NAMESPACE).svc:$(PORT)/reflect"
@echo " Now enable the 'sparc' plugin in an agent's authbridge config (see docs/sparc-plugin.md)."

status: ## Show the SPARC service deployment + config

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: kubectl get deploy,svc,cm/sparc-service-config -l app.kubernetes.io/name=sparc-service — the -l selector also filters the explicitly-named ConfigMap, which install creates without that label, so it won't show on this line. Harmless (the following kubectl ... cm sparc-service-config -o yaml covers it), but the label filter on a named resource is inconsistent.

kubectl -n $(NAMESPACE) get deploy,svc,cm/sparc-service-config -l app.kubernetes.io/name=sparc-service 2>/dev/null; \
kubectl -n $(NAMESPACE) get cm sparc-service-config -o yaml 2>/dev/null | sed -n '/^data:/,/^[a-z]/p'

uninstall: ## Remove the SPARC service, its config, and creds from $(NAMESPACE)
-kubectl -n $(NAMESPACE) delete -f sparc-service.yaml --ignore-not-found
-kubectl -n $(NAMESPACE) delete configmap sparc-service-config --ignore-not-found
-kubectl -n $(NAMESPACE) delete secret sparc-creds --ignore-not-found
@echo "[*] SPARC service removed from $(NAMESPACE)."
Loading
Loading