scobleclaw/scoble-policy/opa/opa-policies-configmap.yaml
hermes 39b21dcda5 feat(opa): Layered judge L0 — OPA deployment + 4 Rego policies (eng#40)
Deploys openpolicyagent/opa:0.68.0 as a single-replica ClusterIP
service in the scobleclaw namespace, with a ConfigMap-mounted
policy bundle under /policies.

Policies (package scobleclaw):
  _lib.rego            shared deny rules (external email, forgejo
                       write outside marcus/engineering, payment
                       kamelets) + helpers (is_external_email,
                       is_payment_kamelet, is_forgejo_write_outside_allowed)
  main-agent.rego      10-kamelet allowlist for main-agent
  demo-concierge.rego  read-only 4-kamelet allowlist
  demo-watchdog.rego   4-kamelet allowlist (ob1 + discord)

L0 contract:
  POST /v1/data/scobleclaw/allow      -> {allow: bool}
  POST /v1/data/scobleclaw/needs_l1   -> {needs_l1: bool}
  POST /v1/data/scobleclaw/deny       -> [reason, ...]

Verified end-to-end against the running OPA pod:
  allow=true  for main-agent + kamelet:source/forgejo-search
  deny=[reason] for external email, forgejo write to stranger/not-mine,
               payment kamelet (stripe-charge), demo-concierge forgejo-create
  needs_l1=true for unknown kamelet (e.g. some-new-kamelet) on main-agent
  needs_l1=false for payment kamelet (L1 cannot override L0 deny)

Out of scope (separate tickets): judge-integration Camel-K binding,
llm-classifier-integration (L1), discord-escalation-integration (L2),
audit-sink OPA log tailing.
2026-06-08 16:36:37 -07:00

137 lines
3.8 KiB
YAML

---
# ConfigMap containing the Rego policy bundle. Mounted at /policies in the
# OPA pod. Policies share the `scobleclaw` package; per-agent files add
# their own allow + agent-specific deny rules on top of _lib's base denies.
apiVersion: v1
kind: ConfigMap
metadata:
name: opa-policies
namespace: scobleclaw
labels:
app: opa
component: l0-judge
data:
_lib.rego: |
package scobleclaw
internal_email_domain = "scoble.me"
payment_kamelets = {
"kamelet:source/stripe-charge",
"kamelet:source/paypal-transfer",
"kamelet:source/ach-debit",
"kamelet:sink/wire-transfer",
}
is_external_email(domain) {
domain != internal_email_domain
domain != ""
}
is_payment_kamelet(ref) {
payment_kamelets[ref]
}
is_forgejo_write_outside_allowed(params) {
params.owner != "marcus"
}
is_forgejo_write_outside_allowed(params) {
params.owner == "marcus"
params.repo != "engineering"
}
deny[reason] {
input.kamelet_ref != ""
input.params.to != ""
is_external_email(input.params.domain)
reason := sprintf("external email not permitted (domain=%v, to=%v)", [input.params.domain, input.params.to])
}
deny[reason] {
is_forgejo_write_outside_allowed(input.params)
reason := sprintf(
"forgejo writes restricted to marcus/engineering (got owner=%v repo=%v)",
[input.params.owner, input.params.repo],
)
}
deny[reason] {
is_payment_kamelet(input.kamelet_ref)
reason := sprintf("payment kamelet blocked at L0: %v", [input.kamelet_ref])
}
main-agent.rego: |
package scobleclaw
main_agent_allowed_kamelets = {
"kamelet:source/forgejo-search",
"kamelet:source/forgejo-read",
"kamelet:source/forgejo-create-issue",
"kamelet:source/forgejo-create-pr",
"kamelet:source/forgejo-comment",
"kamelet:source/ob1-capture",
"kamelet:source/ob1-search",
"kamelet:source/kamelet-dispatch",
"kamelet:source/kubectl-exec",
"kamelet:source/age-decrypt",
}
allow {
input.agent_id == "main-agent"
main_agent_allowed_kamelets[input.kamelet_ref]
count(deny) == 0
}
needs_l1 {
input.agent_id == "main-agent"
not main_agent_allowed_kamelets[input.kamelet_ref]
not payment_kamelets[input.kamelet_ref]
}
demo-concierge.rego: |
package scobleclaw
demo_concierge_allowed_kamelets = {
"kamelet:source/forgejo-search",
"kamelet:source/forgejo-read",
"kamelet:source/ob1-search",
"kamelet:source/kamelet-dispatch",
}
allow {
input.agent_id == "demo-concierge"
demo_concierge_allowed_kamelets[input.kamelet_ref]
count(deny) == 0
}
needs_l1 {
input.agent_id == "demo-concierge"
not demo_concierge_allowed_kamelets[input.kamelet_ref]
not payment_kamelets[input.kamelet_ref]
}
deny[reason] {
input.agent_id == "demo-concierge"
startswith(input.kamelet_ref, "kamelet:source/forgejo-create")
reason := sprintf("demo-concierge is read-only; %v not permitted", [input.kamelet_ref])
}
demo-watchdog.rego: |
package scobleclaw
demo_watchdog_allowed_kamelets = {
"kamelet:source/ob1-search",
"kamelet:source/ob1-list",
"kamelet:source/discord-post",
"kamelet:source/kamelet-dispatch",
}
allow {
input.agent_id == "demo-watchdog"
demo_watchdog_allowed_kamelets[input.kamelet_ref]
count(deny) == 0
}
needs_l1 {
input.agent_id == "demo-watchdog"
not demo_watchdog_allowed_kamelets[input.kamelet_ref]
not payment_kamelets[input.kamelet_ref]
}