guard.enabled (unset is on) makes every policy check nothing while false, live: llmgw ctl guard off/on set it in the cluster's settings, and ctl status, ctl guard list and the guardrails page say when they are off. A refusal by a judge names the hazard it saw - judge: S6,Specialized Advice - and the verdict is kept with the request in a new usage column, guard_rule, shown by ctl request, the request log and the guardrails page. llmgw ctl guard judge ask has a judge read a text, past the cache, and shows its raw answer and how it is read; the endpoint is kept out of the audit so no text is recorded. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
93 lines
3.2 KiB
Go
93 lines
3.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/guard"
|
|
)
|
|
|
|
// GuardAttachments lists which policy is attached where.
|
|
func (s *DB) GuardAttachments(ctx context.Context) ([]guard.Attachment, error) {
|
|
return queryAll(ctx, s.db, `SELECT scope, scope_id, policy FROM guard_attachments ORDER BY scope, scope_id, policy`, nil,
|
|
func(rows *sql.Rows) (guard.Attachment, error) {
|
|
var a guard.Attachment
|
|
err := rows.Scan(&a.Scope, &a.ScopeID, &a.Policy)
|
|
return a, err
|
|
})
|
|
}
|
|
|
|
// AttachGuard attaches a policy to a scope; attaching it again changes
|
|
// nothing.
|
|
func (s *DB) AttachGuard(ctx context.Context, a guard.Attachment, by string) error {
|
|
_, err := s.db.ExecContext(ctx, `INSERT INTO guard_attachments (scope, scope_id, policy, added_at, added_by)
|
|
VALUES (?, ?, ?, ?, ?) ON CONFLICT (scope, scope_id, policy) DO NOTHING`,
|
|
a.Scope, a.ScopeID, a.Policy, millis(time.Now()), by)
|
|
return err
|
|
}
|
|
|
|
// DetachGuard removes one attachment, reporting whether it was there.
|
|
func (s *DB) DetachGuard(ctx context.Context, a guard.Attachment) (bool, error) {
|
|
res, err := s.db.ExecContext(ctx, `DELETE FROM guard_attachments WHERE scope = ? AND scope_id = ? AND policy = ?`,
|
|
a.Scope, a.ScopeID, a.Policy)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
return n > 0, nil
|
|
}
|
|
|
|
// GuardEvent is one request the guardrails acted on, by name only.
|
|
type GuardEvent struct {
|
|
At time.Time
|
|
RequestID string
|
|
KeyID string
|
|
UserID string
|
|
ProjectID string
|
|
Model string
|
|
Status int
|
|
Action string
|
|
Policy string
|
|
Category string
|
|
Rule string
|
|
GuardMs int64
|
|
}
|
|
|
|
// GuardEvents are the newest requests the guardrails did something to —
|
|
// flagged, redacted, blocked, or would have in shadow — since a moment.
|
|
func (s *DB) GuardEvents(ctx context.Context, since time.Time, limit int) ([]GuardEvent, error) {
|
|
return queryAll(ctx, s.db, `SELECT ts, request_id, key_id, COALESCE(user_id, ''), project_id, model, status,
|
|
guard_action, guard_policy, guard_category, guard_rule, guard_ms
|
|
FROM usage WHERE ts >= ? AND guard_action <> '' ORDER BY ts DESC LIMIT ?`,
|
|
[]any{millis(since), limit},
|
|
func(rows *sql.Rows) (GuardEvent, error) {
|
|
var e GuardEvent
|
|
var ts int64
|
|
err := rows.Scan(&ts, &e.RequestID, &e.KeyID, &e.UserID, &e.ProjectID, &e.Model, &e.Status,
|
|
&e.Action, &e.Policy, &e.Category, &e.Rule, &e.GuardMs)
|
|
e.At = fromMillis(ts)
|
|
return e, err
|
|
})
|
|
}
|
|
|
|
// GuardCount is how many requests one combination covers.
|
|
type GuardCount struct {
|
|
ProjectID, Action, Policy, Category string
|
|
Count int64
|
|
}
|
|
|
|
// GuardCounts totals what the guardrails did since a moment, by project,
|
|
// action, policy and category.
|
|
func (s *DB) GuardCounts(ctx context.Context, since time.Time) ([]GuardCount, error) {
|
|
return queryAll(ctx, s.db, `SELECT project_id, guard_action, guard_policy, guard_category, COUNT(*)
|
|
FROM usage WHERE ts >= ? AND guard_action <> ''
|
|
GROUP BY project_id, guard_action, guard_policy, guard_category
|
|
ORDER BY COUNT(*) DESC`, []any{millis(since)},
|
|
func(rows *sql.Rows) (GuardCount, error) {
|
|
var c GuardCount
|
|
err := rows.Scan(&c.ProjectID, &c.Action, &c.Policy, &c.Category, &c.Count)
|
|
return c, err
|
|
})
|
|
}
|