complete MCP tool surface — Matrix approval webhook loop + token verification

Plan #6 (MCP Tool Completion / bin/homelab Migration) done.

- Approval records created for gated request_execution actions
- Notifier sends Matrix messages with HMAC approval tokens
- Stores matrix_event_id, polls /relations/{id}/m.annotation for /
- Reaction detection triggers DecideApproval API call
- Token verification added to DecideApproval endpoint
- Migration 013: matrix_event_id + alert_sent_at on approvals
- AGENTS.md: 21-tool surface documented, stale homelab CLI refs removed
- Plan index updated, audit cross-reference refreshed
This commit is contained in:
2026-07-08 11:02:06 +02:00
parent 5b22f2367b
commit 7c6cffb5f5
12 changed files with 907 additions and 235 deletions

View File

@@ -39,6 +39,8 @@ type Approval struct {
DecidedAt *time.Time
DecidedBy *uuid.UUID
CreatedAt time.Time
MatrixEventID *string
AlertSentAt *time.Time
}
type ApprovalRule struct {

View File

@@ -14,7 +14,7 @@ import (
)
const getApprovalByID = `-- name: GetApprovalByID :one
SELECT entity_id, subject_entity_id, action, risk_class, kind, payload, status, token_hash, expires_at, decided_at, decided_by, created_at FROM approvals WHERE entity_id = $1
SELECT entity_id, subject_entity_id, action, risk_class, kind, payload, status, token_hash, expires_at, decided_at, decided_by, created_at, matrix_event_id, alert_sent_at FROM approvals WHERE entity_id = $1
`
func (q *Queries) GetApprovalByID(ctx context.Context, entityID uuid.UUID) (Approval, error) {
@@ -33,6 +33,8 @@ func (q *Queries) GetApprovalByID(ctx context.Context, entityID uuid.UUID) (Appr
&i.DecidedAt,
&i.DecidedBy,
&i.CreatedAt,
&i.MatrixEventID,
&i.AlertSentAt,
)
return i, err
}
@@ -648,7 +650,7 @@ func (q *Queries) ListApprovalRules(ctx context.Context) ([]ApprovalRule, error)
}
const listApprovals = `-- name: ListApprovals :many
SELECT a.entity_id, a.subject_entity_id, a.action, a.risk_class, a.kind, a.payload, a.status, a.token_hash, a.expires_at, a.decided_at, a.decided_by, a.created_at, e.slug AS subject_slug
SELECT a.entity_id, a.subject_entity_id, a.action, a.risk_class, a.kind, a.payload, a.status, a.token_hash, a.expires_at, a.decided_at, a.decided_by, a.created_at, a.matrix_event_id, a.alert_sent_at, e.slug AS subject_slug
FROM approvals a
JOIN entities e ON e.id = a.subject_entity_id
WHERE ($1::text IS NULL OR a.status = $1)
@@ -676,6 +678,8 @@ type ListApprovalsRow struct {
DecidedAt *time.Time
DecidedBy *uuid.UUID
CreatedAt time.Time
MatrixEventID *string
AlertSentAt *time.Time
SubjectSlug string
}
@@ -701,6 +705,8 @@ func (q *Queries) ListApprovals(ctx context.Context, arg ListApprovalsParams) ([
&i.DecidedAt,
&i.DecidedBy,
&i.CreatedAt,
&i.MatrixEventID,
&i.AlertSentAt,
&i.SubjectSlug,
); err != nil {
return nil, err