Reject empty environment/project, disable FAV consumers at register time, and keep historical classification/repair/replay as audited application use cases with no raw SQL entrypoint. Co-authored-by: Cursor <cursoragent@cursor.com>
122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
package events
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const ClassificationSafe = "safe"
|
|
const ClassificationRepair = "repair"
|
|
const ClassificationQuarantine = "quarantine"
|
|
const ClassificationExpire = "expire"
|
|
|
|
// InventoryRow is a read-only classification of one envelope/delivery.
|
|
type InventoryRow struct {
|
|
EventID string
|
|
Consumer string
|
|
EventName string
|
|
Environment string
|
|
ProjectID int64
|
|
SchemaVersion int
|
|
PayloadDigest string
|
|
Classification string
|
|
Reason string
|
|
}
|
|
|
|
func ClassifyEnvelope(env Envelope, consumer string) InventoryRow {
|
|
row := InventoryRow{
|
|
EventID: env.EventID, Consumer: consumer, EventName: env.EventName,
|
|
Environment: env.Environment, ProjectID: env.ProjectID,
|
|
SchemaVersion: env.SchemaVersion, PayloadDigest: env.PayloadDigest,
|
|
}
|
|
if strings.TrimSpace(env.Environment) == "" || env.ProjectID <= 0 {
|
|
row.Classification = ClassificationQuarantine
|
|
row.Reason = "missing_scope"
|
|
return row
|
|
}
|
|
c, ok := contractByName(env.EventName)
|
|
if !ok {
|
|
row.Classification = ClassificationQuarantine
|
|
row.Reason = "unknown_event"
|
|
return row
|
|
}
|
|
if !c.Enabled {
|
|
row.Classification = ClassificationQuarantine
|
|
row.Reason = "disabled_event"
|
|
return row
|
|
}
|
|
if consumer != "" && (consumer == ConsumerFAV || !containsName(c.Consumers, consumer)) {
|
|
row.Classification = ClassificationQuarantine
|
|
row.Reason = "unknown_or_disabled_consumer"
|
|
return row
|
|
}
|
|
if env.SchemaVersion != c.SchemaVersion {
|
|
row.Classification = ClassificationQuarantine
|
|
row.Reason = "unknown_payload_schema"
|
|
return row
|
|
}
|
|
row.Classification = ClassificationSafe
|
|
row.Reason = "ok"
|
|
return row
|
|
}
|
|
|
|
// RepairRecord is an audited in-place field fix. Callers must persist it;
|
|
// this package never deletes envelopes.
|
|
type RepairRecord struct {
|
|
EventID string
|
|
FieldName string
|
|
OldDigest string
|
|
NewDigest string
|
|
Operator string
|
|
Reason string
|
|
ApprovalRef string
|
|
}
|
|
|
|
func DigestText(v string) string {
|
|
sum := sha256.Sum256([]byte(v))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func ValidateRepair(r RepairRecord) error {
|
|
if r.EventID == "" || r.FieldName == "" || r.Operator == "" || r.Reason == "" || r.ApprovalRef == "" {
|
|
return ErrIntegrityFailure
|
|
}
|
|
if r.OldDigest == "" || r.NewDigest == "" || r.OldDigest == r.NewDigest {
|
|
return ErrIntegrityFailure
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SanitizeSummary(fields map[string]string, allowlist []string) string {
|
|
out := map[string]string{}
|
|
for _, k := range allowlist {
|
|
if v, ok := fields[k]; ok {
|
|
out[k] = v
|
|
}
|
|
}
|
|
b, _ := json.Marshal(out)
|
|
return string(b)
|
|
}
|
|
|
|
func InventoryJSON(rows []InventoryRow) ([]byte, error) {
|
|
type wire struct {
|
|
EventID string `json:"event_id"`
|
|
Consumer string `json:"consumer"`
|
|
Classification string `json:"classification"`
|
|
Reason string `json:"reason"`
|
|
PayloadDigest string `json:"payload_digest"`
|
|
}
|
|
w := make([]wire, 0, len(rows))
|
|
for _, r := range rows {
|
|
w = append(w, wire{r.EventID, r.Consumer, r.Classification, r.Reason, r.PayloadDigest})
|
|
}
|
|
b, err := json.Marshal(w)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inventory: %w", err)
|
|
}
|
|
return b, nil
|
|
}
|