// Package identity is executus's caller-identity seam. // // A CallerID is an opaque string the host assigns (a Discord snowflake, an OAuth // subject, a CI principal). Two optional capabilities hang off it: AdminPolicy // gates authoring-class actions, and MemberResolver supplies per-caller // enrichment (timezone, display name) for tools that want it. Both are nil-safe // so a host that has no notion of "members" or "admins" wires nothing — the // defaults treat everyone as a non-admin unknown. package identity import "context" // Member is optional per-caller enrichment. Attrs carries host-specific extras // (a seerr user id, a persona blurb) without widening this struct per host. type Member struct { ID string DisplayName string Timezone string Attrs map[string]string } // AdminPolicy decides whether a caller may perform authoring-class actions // (saving a shared skill, registering an agent). Default: NonAdmin. type AdminPolicy interface { IsAdmin(ctx context.Context, callerID string) bool } // NonAdmin is the default policy: nobody is an admin. A single-principal host // (a CI job) typically overrides with a constant-true policy for its principal. type NonAdmin struct{} func (NonAdmin) IsAdmin(context.Context, string) bool { return false } // MemberResolver supplies optional enrichment for a CallerID. ok=false means the // member is unknown (the harness then proceeds without enrichment). type MemberResolver interface { Resolve(ctx context.Context, callerID string) (Member, bool) } // IsAdmin is the nil-safe accessor: a nil AdminPolicy denies. func IsAdmin(p AdminPolicy, ctx context.Context, callerID string) bool { if p == nil { return false } return p.IsAdmin(ctx, callerID) } // Resolve is the nil-safe accessor: a nil resolver returns an unknown member. func Resolve(r MemberResolver, ctx context.Context, callerID string) (Member, bool) { if r == nil { return Member{}, false } return r.Resolve(ctx, callerID) }