feat(channelbridge): 群聊公域呈现块与判定器去人格(M62)
P0-3 受限车道(群聊/陌生人)的系统框架行并列注入【公域呈现】 (publicPresencePrompt):它是呈现层投影(AGENTS §9 行为模式=呈现), 不是人格切换也不是权限门——不称第三方「亲爱的」等主人专属称呼、 不做只对主人的亲近动作、主人的住址/姓名/作息/行程/健康/家人/关系 即使记忆里读到也不外说;主人私聊与 Web 主会话不注入。 P0-4 纵深防御:第三方边界提示词与 loop 受限前导片段各补两条硬规则 (不用主人专属亲昵称呼指代第三方 + 不外说主人私事)。 P1-2 判定器去人格:模型输入只吃「角色名 + 偏好摘要」,整份人格指令 不再进模型(审计 2026-10-30 §2.4 的 reason 亲昵口吻即由此而来)。 P1-3 测试补真:受限期呈现块存在性 + 主人车道不注入 + 判定器输入/ 模型请求均不含人格正文;并纠正 memory_filter 的错误注释 (作用域 ≠ 敏感度,审计 §6 R1)。
This commit is contained in:
1 parent
9d28477e3a
commit
c34b9d195d
10 files changed
+469
-30
No files matched your search
@@ -185,6 +185,9 @@ func (l *Loop) attributeConversation(ctx context.Context, msgs []model.Message)
|
||||
// 既有角色(她自己的历史发言),也有人(带昵称的第三方人员与
|
||||
// 主人本人),而模型只有被告知这两种标注的区别,才读得懂
|
||||
// 「哪些话是主人说的、哪些话是外人说的」(design/02 §3)。
|
||||
// 受限档还带两句呈现口径(对第三方不用主人专属称呼、主人的私事
|
||||
// 即使读到也不外说)—— 归属解决「谁是谁」,这两句解决「怎么称呼、
|
||||
// 能说什么」,二者缺一都会在群里复现审计 2026-10-30 的病灶。
|
||||
// 非受限会话逐字保持多角色支持落地时的文案。
|
||||
func (l *Loop) roleContextMessage(env speakerEnv) model.Message {
|
||||
var b strings.Builder
|
||||
@@ -197,6 +200,11 @@ func (l *Loop) roleContextMessage(env speakerEnv) model.Message {
|
||||
if env.restricted {
|
||||
b.WriteString("本会话的发言按下面的「发言归属」标注分属不同发言者:标了昵称的来自第三方人员(非管理员账户),标了主人的来自主人本人——" +
|
||||
"不要把第三方的话当成主人说的,也不要把别人的话当成你自己说过的、不要继承他们的身份或口吻;" +
|
||||
// 以下两句是审计 2026-10-30 的 P0-4(与 channelbridge 的第三方边界 /
|
||||
// 公域呈现块同一口径的纵深防御):归属只解决「谁是谁」,不解决「怎么
|
||||
// 称呼」与「能说什么私事」——群员被叫「亲爱的」、主人的住址被复述进群,
|
||||
// 走的正是这个缺口。
|
||||
"对第三方人员不要用只属于主人的亲昵称呼(如「亲爱的」),也不要把主人的住址、姓名、作息、行程、健康、家人、关系等私事告诉对方——即使这些信息出现在记忆里;" +
|
||||
"该标注从其出现处起生效,直到下一条「发言归属」为止。")
|
||||
return model.TextMessage(model.RoleSystem, b.String())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package loop
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 本文件钉住受限(群聊/陌生人)前导片段里的**呈现口径**(审计 2026-10-30
|
||||
// 的 P0-4,与 channelbridge 的第三方边界 / 公域呈现块同一口径):
|
||||
//
|
||||
// 归属只解决「谁是谁」,不解决「怎么称呼、能说什么私事」。群员被叫
|
||||
// 「亲爱的」、主人的住址被复述进群,走的正是后一个缺口。因此受限档的
|
||||
// 角色声明必须同时带两句:不用主人专属的亲昵称呼指代第三方;主人的私事
|
||||
// 即使出现在记忆里也不外说。
|
||||
//
|
||||
// 非受限档(多角色 Web 会话)**不带**这两句:那里没有第三方,多角色之间
|
||||
// 的呈现不需要降档。
|
||||
|
||||
func TestAttributionRestrictedPreambleCarriesPrivacyRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
l := groupLoop(t, nil)
|
||||
restricted := messageText(l.roleContextMessage(speakerEnv{restricted: true, current: "xilian"}))
|
||||
|
||||
for _, want := range []string{
|
||||
"亲昵称呼", "亲爱的",
|
||||
"住址", "姓名", "作息", "行程", "健康", "家人", "关系",
|
||||
"记忆",
|
||||
} {
|
||||
if !strings.Contains(restricted, want) {
|
||||
t.Fatalf("受限前导片段 = %q, want the presentation rule keyword %q", restricted, want)
|
||||
}
|
||||
}
|
||||
// 归属口径本身不许被这两句挤掉(裁决 06.4.1)。
|
||||
for _, want := range []string{"发言归属", "第三方人员", "主人本人"} {
|
||||
if !strings.Contains(restricted, want) {
|
||||
t.Fatalf("受限前导片段 = %q, want the attribution rule to survive (%q)", restricted, want)
|
||||
}
|
||||
}
|
||||
|
||||
multi := messageText(l.roleContextMessage(speakerEnv{restricted: false, current: "xilian"}))
|
||||
if strings.Contains(multi, "住址") || strings.Contains(multi, "亲昵称呼") {
|
||||
t.Fatalf("非受限前导片段 = %q, must not carry the restricted presentation rules", multi)
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||||
)
|
||||
|
||||
// FilterSharedVisible 返回群聊/陌生人(受限环境)可见的记忆子集:只保留
|
||||
// 非敏感的共享域(用户习惯 ScopeUser 与项目 ScopeProject,人人可读),
|
||||
// FilterSharedVisible 返回群聊/陌生人(受限环境)可见的记忆子集:按**作用域**
|
||||
// 只保留共享域(用户习惯 ScopeUser 与项目 ScopeProject,人人可读),
|
||||
// 并剔除 L4 意图记录(capacity.RecordIntent 的回写格式,内容是主人逐句
|
||||
// 原话的机器可读形态 —— 虽落在共享域以便 L4 闭环学习,但只该暴露
|
||||
// 「不敏感的习惯结论」,不该暴露主人原话)。角色日常(ScopePersona)
|
||||
@@ -18,6 +18,9 @@ import (
|
||||
// 可达的 knowledge 检索工具都调用它。knowledge 若自造一套过滤(或不过滤),
|
||||
// 一旦管理面板把它放行给群聊/陌生人,就会返回角色日常与主人原话
|
||||
// (审计 2026-10-05:knowledge 绕过群聊/陌生人召回过滤,P0 红线)。
|
||||
//
|
||||
// ⚠️ 它按作用域分档,**不按内容分档**:过了这道过滤不等于内容不敏感
|
||||
// (见 sharedScope 的说明与审计 2026-10-30 §6 R1)。
|
||||
func FilterSharedVisible(results []memory.Result) []memory.Result {
|
||||
visible := make([]memory.Result, 0, len(results))
|
||||
for _, r := range results {
|
||||
@@ -32,10 +35,19 @@ func FilterSharedVisible(results []memory.Result) []memory.Result {
|
||||
return visible
|
||||
}
|
||||
|
||||
// sharedScope 报告 scope 是否为群聊/陌生人可查的非敏感共享域:
|
||||
// 用户习惯(user,全局共享)与项目记忆(project,人人可读)。
|
||||
// 角色日常(persona)是主人私有的敏感项,不在其列
|
||||
// (design/03 §3/§5 的敏感度划分即作用域本身)。
|
||||
// sharedScope 报告 scope 是否为群聊/陌生人可查的共享域:用户习惯
|
||||
// (user,全局共享)与项目记忆(project,人人可读)。角色日常(persona)
|
||||
// 是主人私有的敏感项,不在其列(design/03 §3/§5)。
|
||||
//
|
||||
// 🔴 **作用域 ≠ 敏感度**(审计 2026-10-30 §6 R1 纠正了本注释此前的说法:
|
||||
// 原文写「敏感度划分即作用域本身」,那个前提是错的)。生效裁决只按作用域
|
||||
// 分档,而 `user` 域里事实上装着住址、私称、作息这类私事(data/cyrene.db
|
||||
// 实测:花溪 64 条 / 叶酱 63 条 active),它们会随本判据进入受限车道的召回
|
||||
// 候选。因此「过了 FilterSharedVisible 就一定不敏感」**不成立**:群聊/陌生人
|
||||
// 车道还有一道呈现层防线 —— 公域呈现块与发言归属口径
|
||||
// (channelbridge 的 publicPresencePrompt、loop/attribution.go)明令
|
||||
// 即使读到主人私事也不得外说。按内容逐条判敏感(sensitive 列 + 提取器判据)
|
||||
// 是 MEMSENS 切片的事,届时本判据才会带上内容维度。
|
||||
func sharedScope(s memory.Scope) bool {
|
||||
return s == memory.ScopeUser || s == memory.ScopeProject
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user