mirror of
https://github.com/luxfi/aml.git
synced 2026-09-24 00:25:20 +00:00
api: publish the detection catalog with its gaps
The catalog serves the rule set actually installed, not a document describing one, so the coverage claim is the running configuration and cannot drift from it. The published gap list is in the same response deliberately. A catalog that shows only what is covered invites the reader to assume the rest is covered too, and the reader here is a supervisor. Naming what is not detected, with the citation that requires it, is the difference between a coverage claim and a coverage assertion. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Copyright 2024-2026 Lux Partners Limited. All rights reserved.
|
||||
// Use of this source code is governed by the Apache 2.0
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/hanzoai/base/core"
|
||||
|
||||
"github.com/luxfi/aml/pkg/rules"
|
||||
)
|
||||
|
||||
// catalog reports what this deployment detects, what obliges it, and what it does
|
||||
// not detect.
|
||||
//
|
||||
// It serves the rule set actually installed rather than a document describing one,
|
||||
// so the coverage claim is the running configuration and cannot drift from it. The
|
||||
// gap list is published in the same response for the same reason: a catalog that
|
||||
// shows only what is covered invites the reader to assume the remainder is covered
|
||||
// too, and the honest surface names both.
|
||||
func (h *Handler) catalog() func(*core.RequestEvent) error {
|
||||
return func(e *core.RequestEvent) error {
|
||||
if _, err := h.tenant(e); err != nil {
|
||||
return refuse(e, err)
|
||||
}
|
||||
if h.Engine == nil {
|
||||
return fail(e, http.StatusServiceUnavailable, "no engine is wired")
|
||||
}
|
||||
|
||||
installed := h.Engine.Rules()
|
||||
entries := make([]map[string]any, 0, len(installed))
|
||||
for _, r := range installed {
|
||||
entries = append(entries, map[string]any{
|
||||
"id": r.ID,
|
||||
"name": r.Name,
|
||||
"typology": r.Typology,
|
||||
"description": r.Description,
|
||||
"expression": r.DSL,
|
||||
"severity": r.Severity,
|
||||
"action": r.Action,
|
||||
"enabled": r.Enabled,
|
||||
"citations": r.Citations,
|
||||
})
|
||||
}
|
||||
return e.JSON(http.StatusOK, map[string]any{
|
||||
"typologies": rules.Typologies(),
|
||||
"rules": entries,
|
||||
"obligations": rules.Obligations(),
|
||||
"gaps": rules.Gaps(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -185,6 +185,7 @@ func (h *Handler) Register(se *core.ServeEvent) {
|
||||
se.Router.POST("/v1/aml/relationships/search", h.searchRelationships())
|
||||
se.Router.POST("/v1/aml/sanctions/search", h.searchSanctions())
|
||||
se.Router.GET("/v1/aml/sanctions/sources", h.screeningSources())
|
||||
se.Router.GET("/v1/aml/catalog", h.catalog())
|
||||
se.Router.GET("/v1/aml/health", h.health())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user