Files
Cyrene_v2/internal/channel/session/session.go
T

81 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).
//
// 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[:])
}