mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
feat(store): add ActorIDFromContext helper
Introduce ActorIDFromContext(ctx) that returns SenderID if set, else falls back to UserID. Separates ACTOR (acting principal) from SCOPE (memory/namespace) semantics so permission checks, audit trails, and ownership fields can migrate off the scope-coupled UserIDFromContext. No callers yet; migration of existing sites is a follow-up.
This commit is contained in:
1 parent
207bc56dbd
commit
237626b660
2 files changed
+65
No files matched your search
@@ -200,6 +200,25 @@ func SenderIDFromContext(ctx context.Context) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ActorIDFromContext returns the acting principal — the entity performing
|
||||
// the action. In group chats (Telegram/Discord) this is the individual
|
||||
// sender; in DMs / HTTP / cron it is the same as UserIDFromContext.
|
||||
//
|
||||
// Use for:
|
||||
// - permission checks (file writers, role gates)
|
||||
// - audit trails (initiated_by, owner_id)
|
||||
// - ownership fields (skill publisher, cron owner)
|
||||
//
|
||||
// DO NOT use for:
|
||||
// - memory / KG / session scope (use UserIDFromContext / MemoryUserID / KGUserID)
|
||||
// - file-system or per-scope isolation (scope = group principal on purpose)
|
||||
func ActorIDFromContext(ctx context.Context) string {
|
||||
if sid := SenderIDFromContext(ctx); sid != "" {
|
||||
return sid
|
||||
}
|
||||
return UserIDFromContext(ctx)
|
||||
}
|
||||
|
||||
// WithSelfEvolve returns a new context with the self-evolve flag.
|
||||
func WithSelfEvolve(ctx context.Context, v bool) context.Context {
|
||||
return context.WithValue(ctx, SelfEvolveKey, v)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestActorIDFromContext_SenderSetReturnsSender(t *testing.T) {
|
||||
ctx := WithUserID(context.Background(), "group:telegram:-100123")
|
||||
ctx = WithSenderID(ctx, "42")
|
||||
if got := ActorIDFromContext(ctx); got != "42" {
|
||||
t.Errorf("sender_set: got %q, want %q", got, "42")
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorIDFromContext_SenderEmptyFallbackToUser(t *testing.T) {
|
||||
ctx := WithUserID(context.Background(), "user-7")
|
||||
if got := ActorIDFromContext(ctx); got != "user-7" {
|
||||
t.Errorf("fallback_to_user: got %q, want %q", got, "user-7")
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorIDFromContext_BothSetSenderWins(t *testing.T) {
|
||||
ctx := WithUserID(context.Background(), "group:discord:G|U")
|
||||
ctx = WithSenderID(ctx, "123456")
|
||||
if got := ActorIDFromContext(ctx); got != "123456" {
|
||||
t.Errorf("both_set_sender_wins: got %q, want %q", got, "123456")
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorIDFromContext_NeitherSetReturnsEmpty(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
if got := ActorIDFromContext(ctx); got != "" {
|
||||
t.Errorf("neither_set: got %q, want %q", got, "")
|
||||
}
|
||||
}
|
||||
|
||||
// SenderID with "|" delimiter (Discord guild:user|user format) is returned as-is —
|
||||
// ActorIDFromContext is not responsible for numeric split; callers do that themselves
|
||||
// (see config_permission_store.go:68 strings.SplitN(senderID, "|", 2)[0]).
|
||||
func TestActorIDFromContext_DelimitedSenderReturnedAsIs(t *testing.T) {
|
||||
ctx := WithSenderID(context.Background(), "42|extra")
|
||||
if got := ActorIDFromContext(ctx); got != "42|extra" {
|
||||
t.Errorf("delimited_sender_as_is: got %q, want %q", got, "42|extra")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user