feat: modernise to Go 1.26 iterators and stdlib helpers
Add iter.Seq iterators for trust registry (ListSeq), audit log (EventsSeq, QuerySeq), and approval store (PendingSeq). Use slices.DeleteFunc in session store, slices.SortFunc in testcmd, range-over-int in benchmarks. Co-Authored-By: Gemini <noreply@google.com> Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
9fdbe9db6f
commit
ee58e790a0
11 changed files with 147 additions and 29 deletions
|
|
@ -2,6 +2,7 @@ package auth
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -75,11 +76,9 @@ func (m *MemorySessionStore) DeleteByUser(userID string) error {
|
|||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for token, session := range m.sessions {
|
||||
if session.UserID == userID {
|
||||
delete(m.sessions, token)
|
||||
}
|
||||
}
|
||||
maps.DeleteFunc(m.sessions, func(token string, session *Session) bool {
|
||||
return session.UserID == userID
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -90,11 +89,12 @@ func (m *MemorySessionStore) Cleanup() (int, error) {
|
|||
|
||||
now := time.Now()
|
||||
count := 0
|
||||
for token, session := range m.sessions {
|
||||
maps.DeleteFunc(m.sessions, func(token string, session *Session) bool {
|
||||
if now.After(session.ExpiresAt) {
|
||||
delete(m.sessions, token)
|
||||
count++
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
return count, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package testcmd
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"cmp"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -119,8 +120,8 @@ func printCoverageSummary(results testResults) {
|
|||
fmt.Printf("\n %s\n", testHeaderStyle.Render(i18n.T("cmd.test.coverage_by_package")))
|
||||
|
||||
// Sort packages by name
|
||||
sort.Slice(results.packages, func(i, j int) bool {
|
||||
return results.packages[i].name < results.packages[j].name
|
||||
slices.SortFunc(results.packages, func(a, b packageCoverage) int {
|
||||
return cmp.Compare(a.name, b.name)
|
||||
})
|
||||
|
||||
// Find max package name length for alignment
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func BenchmarkArgon2Derive(b *testing.B) {
|
|||
_, _ = rand.Read(salt)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_ = DeriveKey(passphrase, salt, argon2KeyLen)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ func BenchmarkChaCha20Encrypt_1KB(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, _ = ChaCha20Encrypt(plaintext, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ func BenchmarkChaCha20Encrypt_1MB(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(1024 * 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, _ = ChaCha20Encrypt(plaintext, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ func BenchmarkAESGCMEncrypt_1KB(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, _ = AESGCMEncrypt(plaintext, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ func BenchmarkAESGCMEncrypt_1MB(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(1024 * 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_, _ = AESGCMEncrypt(plaintext, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ func BenchmarkHMACSHA256_1KB(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.SetBytes(1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_ = HMACSHA256(message, key)
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ func BenchmarkVerifyHMACSHA256(b *testing.B) {
|
|||
mac := HMACSHA256(message, key)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for range b.N {
|
||||
_ = VerifyHMAC(message, key, mac, sha256.New)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
go.sum
3
go.sum
|
|
@ -1,6 +1,9 @@
|
|||
forge.lthn.ai/core/cli v0.0.1 h1:nqpc4Tv8a4H/ERei+/71DVQxkCFU8HPFJP4120qPXgk=
|
||||
forge.lthn.ai/core/cli v0.0.1/go.mod h1:xa3Nqw3sUtYYJ1k+1jYul18tgs6sBevCUsGsHJI1hHA=
|
||||
forge.lthn.ai/core/go v0.0.1 h1:ubk4nmkA3treOUNgPS28wKd1jB6cUlEQUV7jDdGa3zM=
|
||||
forge.lthn.ai/core/go v0.0.1/go.mod h1:59YsnuMaAGQUxIhX68oK2/HnhQJEPWL1iEZhDTrNCbY=
|
||||
forge.lthn.ai/core/go-store v0.1.0 h1:ONO4NfnFVey2QOE5JAZp5dQPI2pxRCHWAtQ+oYFJgGE=
|
||||
forge.lthn.ai/core/go-store v0.1.0/go.mod h1:FpUlLEX/ebyoxpk96F7ktr0vYvmFtC5Rpi9fi88UVqw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package trust
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -166,6 +167,22 @@ func (q *ApprovalQueue) Pending() []ApprovalRequest {
|
|||
return out
|
||||
}
|
||||
|
||||
// PendingSeq returns an iterator over all requests with ApprovalPending status.
|
||||
func (q *ApprovalQueue) PendingSeq() iter.Seq[ApprovalRequest] {
|
||||
return func(yield func(ApprovalRequest) bool) {
|
||||
q.mu.RLock()
|
||||
defer q.mu.RUnlock()
|
||||
|
||||
for _, req := range q.requests {
|
||||
if req.Status == ApprovalPending {
|
||||
if !yield(*req) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the total number of requests in the queue.
|
||||
func (q *ApprovalQueue) Len() int {
|
||||
q.mu.RLock()
|
||||
|
|
|
|||
|
|
@ -201,6 +201,22 @@ func TestApprovalPending_Good_Empty(t *testing.T) {
|
|||
assert.Empty(t, q.Pending())
|
||||
}
|
||||
|
||||
func TestApprovalPendingSeq_Good(t *testing.T) {
|
||||
q := NewApprovalQueue()
|
||||
q.Submit("Clotho", CapMergePR, "host-uk/core")
|
||||
q.Submit("Hypnos", CapMergePR, "host-uk/docs")
|
||||
|
||||
id3, _ := q.Submit("Darbs", CapMergePR, "host-uk/tools")
|
||||
q.Approve(id3, "admin", "")
|
||||
|
||||
count := 0
|
||||
for req := range q.PendingSeq() {
|
||||
assert.Equal(t, ApprovalPending, req.Status)
|
||||
count++
|
||||
}
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
// --- Concurrent operations ---
|
||||
|
||||
func TestApprovalConcurrent_Good(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -103,6 +104,20 @@ func (l *AuditLog) Entries() []AuditEntry {
|
|||
return out
|
||||
}
|
||||
|
||||
// EntriesSeq returns an iterator over all audit entries.
|
||||
func (l *AuditLog) EntriesSeq() iter.Seq[AuditEntry] {
|
||||
return func(yield func(AuditEntry) bool) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
for _, e := range l.entries {
|
||||
if !yield(e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of entries in the log.
|
||||
func (l *AuditLog) Len() int {
|
||||
l.mu.Lock()
|
||||
|
|
@ -123,3 +138,19 @@ func (l *AuditLog) EntriesFor(agent string) []AuditEntry {
|
|||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// EntriesForSeq returns an iterator over audit entries for a specific agent.
|
||||
func (l *AuditLog) EntriesForSeq(agent string) iter.Seq[AuditEntry] {
|
||||
return func(yield func(AuditEntry) bool) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
for _, e := range l.entries {
|
||||
if e.Agent == agent {
|
||||
if !yield(e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,26 @@ func TestAuditEntriesFor_Good(t *testing.T) {
|
|||
for _, e := range athenaEntries {
|
||||
assert.Equal(t, "Athena", e.Agent)
|
||||
}
|
||||
|
||||
// Test iterator version
|
||||
count := 0
|
||||
for e := range log.EntriesForSeq("Athena") {
|
||||
assert.Equal(t, "Athena", e.Agent)
|
||||
count++
|
||||
}
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestAuditEntriesSeq_Good(t *testing.T) {
|
||||
log := NewAuditLog(nil)
|
||||
log.Record(EvalResult{Agent: "Athena", Cap: CapPushRepo, Decision: Allow, Reason: "ok"}, "")
|
||||
log.Record(EvalResult{Agent: "Clotho", Cap: CapCreatePR, Decision: Allow, Reason: "ok"}, "")
|
||||
|
||||
count := 0
|
||||
for range log.EntriesSeq() {
|
||||
count++
|
||||
}
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestAuditEntriesFor_Bad_NotFound(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func BenchmarkPolicyEvaluate(b *testing.B) {
|
|||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := range b.N {
|
||||
agentName := fmt.Sprintf("agent-%d", i%100)
|
||||
cap := caps[i%len(caps)]
|
||||
_ = pe.Evaluate(agentName, cap, "host-uk/core")
|
||||
|
|
@ -49,7 +49,7 @@ func BenchmarkRegistryGet(b *testing.B) {
|
|||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := range b.N {
|
||||
name := fmt.Sprintf("agent-%d", i%100)
|
||||
_ = r.Get(name)
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ func BenchmarkRegistryRegister(b *testing.B) {
|
|||
r := NewRegistry()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for i := range b.N {
|
||||
_ = r.Register(Agent{
|
||||
Name: fmt.Sprintf("bench-agent-%d", i),
|
||||
Tier: TierVerified,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ package trust
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -51,15 +52,15 @@ func (t Tier) Valid() bool {
|
|||
type Capability string
|
||||
|
||||
const (
|
||||
CapPushRepo Capability = "repo.push"
|
||||
CapMergePR Capability = "pr.merge"
|
||||
CapCreatePR Capability = "pr.create"
|
||||
CapCreateIssue Capability = "issue.create"
|
||||
CapCommentIssue Capability = "issue.comment"
|
||||
CapReadSecrets Capability = "secrets.read"
|
||||
CapRunPrivileged Capability = "cmd.privileged"
|
||||
CapPushRepo Capability = "repo.push"
|
||||
CapMergePR Capability = "pr.merge"
|
||||
CapCreatePR Capability = "pr.create"
|
||||
CapCreateIssue Capability = "issue.create"
|
||||
CapCommentIssue Capability = "issue.comment"
|
||||
CapReadSecrets Capability = "secrets.read"
|
||||
CapRunPrivileged Capability = "cmd.privileged"
|
||||
CapAccessWorkspace Capability = "workspace.access"
|
||||
CapModifyFlows Capability = "flows.modify"
|
||||
CapModifyFlows Capability = "flows.modify"
|
||||
)
|
||||
|
||||
// Agent represents an agent identity in the trust system.
|
||||
|
|
@ -143,6 +144,19 @@ func (r *Registry) List() []Agent {
|
|||
return out
|
||||
}
|
||||
|
||||
// ListSeq returns an iterator over all registered agents.
|
||||
func (r *Registry) ListSeq() iter.Seq[Agent] {
|
||||
return func(yield func(Agent) bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
for _, a := range r.agents {
|
||||
if !yield(*a) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the number of registered agents.
|
||||
func (r *Registry) Len() int {
|
||||
r.mu.RLock()
|
||||
|
|
|
|||
|
|
@ -151,6 +151,22 @@ func TestRegistryList_Good_Snapshot(t *testing.T) {
|
|||
assert.Equal(t, TierFull, r.Get("Athena").Tier)
|
||||
}
|
||||
|
||||
func TestRegistryListSeq_Good(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
require.NoError(t, r.Register(Agent{Name: "Athena", Tier: TierFull}))
|
||||
require.NoError(t, r.Register(Agent{Name: "Clotho", Tier: TierVerified}))
|
||||
|
||||
count := 0
|
||||
names := make(map[string]bool)
|
||||
for a := range r.ListSeq() {
|
||||
names[a.Name] = true
|
||||
count++
|
||||
}
|
||||
assert.Equal(t, 2, count)
|
||||
assert.True(t, names["Athena"])
|
||||
assert.True(t, names["Clotho"])
|
||||
}
|
||||
|
||||
// --- Agent ---
|
||||
|
||||
func TestAgentTokenExpiry(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue