access-engine/internal/audit/log.go
tegwick f758381cea
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 6s
Build and Publish Container Image / build-and-push (push) Successful in 1m23s
FLEX-WP-0036: Go module is github.com/netkingdom/access-engine; T02 consumer inventory
- module path renamed (no external importers); golangci local-prefixes
- ACCESS_ENGINE_RUN_TOPAZ_INTEGRATION replaces FLEX_AUTH_RUN_TOPAZ_INTEGRATION
- README title and identity paragraph reflect the landed renames
- docs/runtime-rename-inventory.md: per-consumer references by kind and order
- chart name, image package and /opt paths deliberately left for T05

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 735967@bnt-lap001
Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
2026-10-10 15:29:00 +02:00

94 lines
2.3 KiB
Go

package audit
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"github.com/netkingdom/access-engine/pkg/api"
)
// JSONLDecisionLog persists compact decision envelopes for local development.
type JSONLDecisionLog struct {
path string
mu sync.Mutex
}
// NewJSONLDecisionLog returns a JSONL-backed decision log.
func NewJSONLDecisionLog(path string) *JSONLDecisionLog {
return &JSONLDecisionLog{path: path}
}
// Append writes one decision envelope as one JSON line.
func (l *JSONLDecisionLog) Append(decision api.DecisionEnvelope) error {
l.mu.Lock()
defer l.mu.Unlock()
if err := os.MkdirAll(filepath.Dir(l.path), 0o755); err != nil {
return fmt.Errorf("create decision log directory: %w", err)
}
file, err := os.OpenFile(l.path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("open decision log: %w", err)
}
defer file.Close()
data, err := json.Marshal(decision)
if err != nil {
return fmt.Errorf("marshal decision envelope: %w", err)
}
if _, err := file.Write(append(data, '\n')); err != nil {
return fmt.Errorf("write decision log: %w", err)
}
return nil
}
// ReadAll returns every decision envelope from the log in file order.
func (l *JSONLDecisionLog) ReadAll() ([]api.DecisionEnvelope, error) {
l.mu.Lock()
defer l.mu.Unlock()
file, err := os.Open(l.path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, fmt.Errorf("open decision log: %w", err)
}
defer file.Close()
var decisions []api.DecisionEnvelope
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if scanner.Text() == "" {
continue
}
var decision api.DecisionEnvelope
if err := json.Unmarshal(scanner.Bytes(), &decision); err != nil {
return nil, fmt.Errorf("unmarshal decision log line %d: %w", len(decisions)+1, err)
}
decisions = append(decisions, decision)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("read decision log: %w", err)
}
return decisions, nil
}
// Find returns one decision envelope by id.
func (l *JSONLDecisionLog) Find(id string) (api.DecisionEnvelope, bool, error) {
decisions, err := l.ReadAll()
if err != nil {
return api.DecisionEnvelope{}, false, err
}
for _, decision := range decisions {
if decision.ID == id {
return decision, true, nil
}
}
return api.DecisionEnvelope{}, false, nil
}