- 适配器只描述平台身份:qq_obv11 的 owner/admin 一律上报 RoleMember,不再上报 RoleOwner - session 层唯一判主点只认白名单(owner.yaml Accounts ∪ 每渠道 adminIds),nil 档案 fail-closed 拒绝 - 新增「每渠道管理员层」:channels.json ChannelAccount.adminIds[] → runtimeconfig.AdminIDs → OwnerProfile.SetPlatformAdmins(整体替换语义,热重载后移除的 id 立即失效) - 组合根接线:启动即播种、面板保存先灌白名单再重载适配器(cmd/cyrene/wire_channels.go + 4 条端到端单测) - 06.4.3 节流与参与度状态按会话独立计数(ConversationKey 三处统一,空会话 GC);06.4.4 未知平台主动发言保守默认 1 条/分钟
82 lines
3.5 KiB
Go
82 lines
3.5 KiB
Go
// Package session maps IM channels to internal sessions and resolves the
|
|
// strongly typed actor identity that gates every downstream permission decision.
|
|
//
|
|
// It is the M6 home of three cross-cutting concerns wired together at the
|
|
// channel layer, per DevDocs/design/11 §0/§2:
|
|
//
|
|
// - Channel↔session mapping: a persistent (platform, channel_id) → session_id
|
|
// map (SQLite table channel_sessions), so the same conversation always maps
|
|
// to the same internal session across restarts.
|
|
// - Owner recognition: a structural, never-prompt rule. ResolveActor decides
|
|
// owner vs group_member vs stranger purely from channel-level actor metadata
|
|
// matched against the user.OwnerProfile account whitelist. Nothing here reads
|
|
// message text to infer identity — that is a project red line.
|
|
// - Proactive push policy: per-platform rules from design/08 §3 (weixin-ilink
|
|
// 24h window, OneBot burst rate limit, telegram/webhook unrestricted), with a
|
|
// conservative strict limit as the default for unknown platforms (裁决 06.4.4).
|
|
//
|
|
// The package is concurrency-safe: the manager guards its cache with an
|
|
// RWMutex and the rate limiter uses its own mutex, so multiple adapters may
|
|
// resolve sessions and issue pushes concurrently.
|
|
package session
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
)
|
|
|
|
// Package-level sentinel errors. Each is wrapped with %w at the call site so
|
|
// callers can use errors.Is.
|
|
var (
|
|
// ErrNilDB is returned when SessionManager is built with a nil database.
|
|
ErrNilDB = errors.New("session: nil database handle")
|
|
// ErrInvalidChannel is returned when a message has no usable channel id.
|
|
ErrInvalidChannel = errors.New("session: empty channel id")
|
|
)
|
|
|
|
// sessionIDPrefix distinguishes generated session ids from foreign identifiers.
|
|
const sessionIDPrefix = "sess-"
|
|
|
|
// Session is the resolved channel↔session binding for one inbound message. It
|
|
// carries the identity metadata (ActorRole, Owner, IsGroup) that the policy
|
|
// engine and memory layer read, plus the isolation flag MemWritable which says
|
|
// whether this conversation may write to shared memory.
|
|
//
|
|
// The memory rule follows design/02 §3 and design/11 §2: only the owner's
|
|
// private conversation may read/write memory shared with the Web main session;
|
|
// group chats and stranger conversations may read memory read-only and may
|
|
// never persist anything that could leak owner-sensitive information.
|
|
type Session struct {
|
|
// Platform is the normalized platform identifier (e.g. "weixin-ilink").
|
|
Platform string
|
|
// ChannelID is the stable channel key used for session mapping.
|
|
ChannelID string
|
|
// SessionID is the internal session binding the channel maps to.
|
|
SessionID string
|
|
// ActorRole is the strongly typed role of the resolved actor.
|
|
ActorRole adapter.Role
|
|
// Owner reports whether the actor is the bound owner account.
|
|
Owner bool
|
|
// IsGroup reports whether this is a group/room conversation.
|
|
IsGroup bool
|
|
// MemWritable reports whether this conversation may write shared memory.
|
|
// It is true only for the owner's private conversation.
|
|
MemWritable bool
|
|
}
|
|
|
|
// newSessionID returns a random, collision-resistant session identifier.
|
|
func newSessionID() string {
|
|
var b [16]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
// rand.Read should never fail; fall back to a nanosecond-named id so a
|
|
// session can still be created rather than failing the whole call.
|
|
return fmt.Sprintf("%s%d", sessionIDPrefix, time.Now().UnixNano())
|
|
}
|
|
return sessionIDPrefix + hex.EncodeToString(b[:])
|
|
}
|