跳到正文
曾亮懿 ZengLiangYi Blog

团队知识共享:令牌、审批与实例边界

区分当前云令牌与拟议成员权限,以合成网关规则验证只读操作和整批审批,并明确共享与备份责任。

曾亮懿

曾亮懿

阅读约 5 分钟
目录 展开 收起

团队知识共享有两条边界:哪些内容被批准离开个人机器,哪些主体可以对共享实例执行哪些操作。ChatCrystal 的云模式已经支持令牌和远端归一化导入,但固定提交没有为每位成员建立独立账号或项目级读写角色。把一个共享 token 发给所有人,不等于实现权限隔离。

当前云模式的真实能力

server/src/runtime/cloud.ts 通过 CHATCRYSTAL_CLOUD_MODE=true 或非空 CHATCRYSTAL_API_TOKEN 判定云模式。server/src/routes/setup.ts 为受保护 API 路由安装 Bearer token 检查;health、setup 状态/完成和 auth verify 属于公开例外,setup、verify 与 token rotation 另有各自校验。rotate 路由用请求体的 currentToken 验证,不能概括为所有路由只检查 Bearer。server/src/services/auth.ts 支持环境令牌或 scrypt 保存的单令牌及失败尝试控制;API 只轮换持久化令牌,环境令牌要在部署环境更改。这不是多用户账号、逐成员撤销或 RBAC。

server/src/routes/import.ts 在云模式禁用服务端本地扫描,开放 ingest 路径;server/src/cli/commands/import.ts 从有源历史的设备解析并上传归一化载荷,非交互需明确 –yes。server/src/runtime/watcherPolicy.ts 云模式不启动 watcher。因此“共享主机自动扫描所有人的桌面文件”和“只有单人本地导入,没有远端入口”都不是当前行为。多个客户端可以提交,不表示同时导入压力、隔离或冲突恢复已经实测。

MCP 的七工具通过客户端使用同一 API 连接,见MCP 协议边界。一套工具名称不会证明接的是哪个团队数据集,也不会替共享网关制定成员权限。

选择共享形态前明确信任范围

形态 保留的边界 需要额外解决
个人库与经审查的可读导出 私人历史先留在本人设备 导出版本、脱敏、来源和更新记录
一个共同信任域内的共享实例 当前单令牌访问 所有持令牌者的能力范围、轮换影响、并发恢复
身份网关后的共享实例 可在外围制定逐人/逐角色规则 路由与方法完整验证、阻断直连、网关维护和审计
不同信任域分别部署 数据与凭据分开 多实例更新与检索策略

这些是部署方案,不是当前 UI 内置团队功能,也没有人数或内存推荐实测。tag、project_key 和 scope 是检索或知识语义,不是 token 的权限分区。手动全局知识和自动项目知识的区别,不能用来承诺某成员无法读另一个项目。

共享前先审查内容,读权限按操作定义

源对话可能包含路径、代码、凭据或未审定结论。共享流程应先得到数据所有者批准,标记可共享范围、脱敏、记录原始来源,再上传;自动摘要不能被当作自动脱敏保证。服务端 ingest 是数据入口,命令确认不是组织审批机制。

只允许 GET 会阻断 POST /api/memory/recall 这类读性质的操作,又可能放行带敏感配置的 GET。角色规则必须按准确方法和路径设计,再测实际客户端功能;同时从网络层禁止绕过网关直连持共享令牌的后端。下面是拟议网关策略和整批审查的合成验收,不是 ChatCrystal 的鉴权实现。角色作为参数传入,没有身份认证、HTTP 代理、真实脱敏器、真实导入或后端。

用 Node 24 保存为 team.mjs,运行 node team.mjs。

import assert from 'node:assert/strict';

// Proposed gateway policy; this is not ChatCrystal's token hook.
const rules = new Map([
  ['reader', new Set(['POST /api/memory/recall', 'GET /api/notes'])],
  ['importer', new Set(['POST /api/import/ingest'])],
]);
function authorize(role, method, rawPath, target, approvedTarget) {
  assert.equal(target, approvedTarget);
  const url = new URL(rawPath, 'https://gateway.invalid');
  if (url.pathname.includes('%') || url.pathname.includes('..')) throw new Error('ambiguous path');
  if (!rules.get(role)?.has(`${method} ${url.pathname}`)) throw new Error('denied');
}
const target = 'reviewed-instance-a';
let stored = [];
function ingest(role, items) {
  authorize(role, 'POST', '/api/import/ingest', target, target);
  for (const item of items) {
    if (item.approval !== 'approved' || item.classification !== 'team-shareable')
      throw new Error('unapproved input');
    if (/secret|password|token/i.test(item.content)) throw new Error('redaction required');
  }
  stored.push(...items.map(({ id, content }) => ({ id, content })));
}
authorize('reader', 'POST', '/api/memory/recall', target, target);
assert.throws(() => authorize('reader', 'POST', '/api/import/ingest', target, target), /denied/);
assert.throws(() => authorize('reader', 'GET', '/api/notes', 'other-instance', target));
assert.throws(() => authorize('reader', 'DELETE', '/api/notes/1', target, target), /denied/);
assert.throws(() => authorize('reader', 'POST', '/api/queue/cancel', target, target), /denied/);
assert.throws(() => authorize('reader', 'GET', '/api/config', target, target), /denied/);
assert.throws(() => authorize('unknown', 'GET', '/api/notes', target, target), /denied/);
const approved = {
  id: 'reviewed-1',
  content: 'use a scoped transaction',
  approval: 'approved',
  classification: 'team-shareable',
};
ingest('importer', [approved]);
assert.equal(stored.length, 1);
assert.throws(
  () => ingest('importer', [approved, { ...approved, id: 'private-2', approval: 'pending' }]),
  /unapproved/
);
assert.equal(stored.length, 1);
assert.throws(
  () => ingest('importer', [{ ...approved, content: 'password=example' }]),
  /redaction/
);
assert.equal(stored.length, 1);
console.log('proposed gateway roles and whole-batch review gate pass on fixtures');

权限与审批验收必须有拒绝样本

删除规则匹配保护,reader 的 ingest 拒绝断言应失败;把 stored.push 移到所有输入审查之前,包含 pending 内容的批次会污染合成存储,数量断言应失败。恢复后运行。关键词检查只证明样本被拦截,不是可靠脱敏或敏感信息检测;URL 和方法规则也不是完整代理安全实现。

上线共享方案要分别测缺/错 token、轮换后旧 token、reader 的 recall 与禁止写入、未批准载荷、错误目标实例、绕过网关直连、恢复后检索。当前 server/src/services/memory/writeback.ts 的写回 receipt 与 server/src/services/experience/reviews.ts 的质量反馈记录有业务用途,但不能当作完整逐成员操作审计;共享 token 也不能可靠归因到个人。身份、审计和内容审批若由外围系统承担,应把负责人和保留周期写入部署约定。

同步与备份不靠复制活动数据库

不要把多人桌面同时指向同一活动 chatcrystal.db,也不要用文件同步覆盖运行中的库。当前 sql.js 在内存中操作并导出,vectra-index 又是独立数据集,详见WASM 持久化和向量索引内部边界。停止所有写入者或取得一致性快照,再复制约定的数据目录并在隔离环境恢复验证。读取源工具的活动 SQLite 还需要主库/WAL 一致性条件,不能靠复制一个 state.vscdb 宣布备份完整。

共享不是把所有历史集中存放。先对相同任务测试 approved 内容的导入、来源追溯、检索、拒绝与恢复,再决定哪些知识需要汇入共同库。自动化步骤见CLI 知识流水线。

分享:

相关文章

AI 编程对话复盘:分母、质量抽检与可复用证据

以合成样本核对来源与长度分母、错收漏收和空样本,并区分当前经验筛选、模型摘要与真实复用结果。

曾亮懿 曾亮懿
阅读约 5 分钟
#ChatCrystal #Knowledge Management #Evaluation

AI 对话格式差异:事件、快照与跨库消息定位

对照五个固定适配器,区分 JSONL 容器、事件语义与 SQLite 键布局,用两个内存数据库观察消息索引、孤立数据和未知版本。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

AI 对话管理方案:四种存储路径与迁移验收

比较 Claude Code、Cursor、Copilot、Trae 的固定适配器输入,区分原始副本、可读导出与知识库,并用合成清单验证身份和信息保留。

曾亮懿 曾亮懿
阅读约 4 分钟
#ChatCrystal #Node.js #SQLite

从文章到代码

到我的 GitHub 看看 ChatCrystal、开发者工具和日常学习记录,欢迎交流实现细节。