Skip to content

[design] 架构设计建议合集(A1–A9,非阻塞):builtin/server 双向依赖、中心文件过大、传输层承载领域规则等 #2085

Open

Description

@dobet

以下是架构评审(master 分支 e4efb39b)的设计建议合集(非阻塞项)。如无异议,我可以按条目拆分为独立 issue 供逐一讨论。

A1. builtin 与 server、client 之间存在双向依赖

  • builtin → server.authz:atomic_memory_search.py:21(模块级)、atomic_memory.py:116、atomic_memory_processing.py:483、relational.py:1045、artifact_merge.py:67,98、composition.py:2140(创建 ACCESS_TABLES)
  • builtin → server.processing_security:topic_memory_processing.py:1598,1794、family_processing.py:55,145、dream_processing.py:29,42
  • builtin → client:decision_projection.py:34 引用 client.capture 做脱敏
  • 反向:server/authz/{composition,repository}.py 与 server/processing_security.py 引用 builtin.persistence 和 builtin.runtime.composition,循环仅靠函数内延迟导入避开。
  • 私有访问:atomic_memory.py:113 读取 context.access._deferred_decisions,即服务端 AccessControlService 的私有属性;核心字段 ArtifactSearchExecutionContext.access: Any(artifacts/search.py:62)掩盖了这种耦合。
  • 领域内部的循环:
    • Profile ↔ Review:artifacts/profile/review.py:39-147 使用约 10 个 ReviewService 私有成员。
    • Review ↔ Dream:Dream 直接对 Review 的 CANDIDATE_HEADS_TABLE 执行原始 UPDATE 取行锁(dream/service.py 约 :622-629)。
    • Prompt 包:artifacts/prompt/builtin.py:19-60 引用所有族,而 Handoff、Profile 又引用 prompt.service。
    • Experience ↔ Work:experience/recurrence.py:35 与 work/models.py:25-34 互相引用。
    • builtin/code/cli.py:26 引用 powercontext.server.configuration。

方向:授权是运行时的领域关注点,适合抽成独立包,供 builtin 与 server 共同依赖;access 改为有类型的 Protocol。D1(仅安装 [builtin] 无法导入)是这一循环依赖的直接后果。

A2. 中心文件过大,功能扩展集中修改少数文件

  • server/app.py(5965 行):一个 create_app 注册全部 123 个操作(:1418-1543),另含 40 个 Protocol 端口(:863-1300)、约 30 个鉴权解析器(:4747-5170)与完整的错误映射(:5433-5965)。只有 Atomic Memory 路由被拆出。
  • builtin/runtime/application.py(4278 行):
    • BuiltinRuntime.__init__ 接收 57 个关键字依赖(:3622-3687),暴露约 20 个功能门面。
    • 44 个类覆盖约 15 个领域,引用 17 个 builtin.* 子包,文件内有 231 处 self._runtime._private 访问。
    • ScopedContextApplication(:951-1874,约 920 行)只有一个公开方法,包含召回与充分性循环,与 prepared_context.py、recall_sufficiency.py 职责重叠。
  • builtin/runtime/composition.py:open_builtin_runtime(:480-868)有 34 个参数,传递约 75 个关键字参数。
  • 修改频率:最近 200 个提交中,composition.py 被修改 19 次,application.py 17 次,relational.py 13 次。
  • 其他大文件:cli/system.py(2618 行)、client/cli.py(1998 行)、server/authz/service.py(1797 行)。

A3. 传输层承载了领域规则

  • acknowledge_handoff(app.py:3176-3209):接收方必须与主体一致的规则,以及 receipt 的预留与提交协议。
  • approve_candidate(app.py:4260-4302):终态候选的重试语义与 owner 建立。
  • list_sources(app.py:2268-2294):为条目补充 receipt 身份,未就绪时返回 503。
  • _reject_reserved_handoff_receipt_content(app.py:3228)。
  • 族相关的授权规则,如 prompt 与 topic memory 写入要求 scope admin(app.py:4999-5002)。
  • 错误映射中嵌入旧版记忆迁移指引(app.py:5470-5530)。
  • app.py:4469-4477 访问 atomic._application(私有),在独立事务中对 atomic memory 标签写入做预鉴权。运行时已在写事务内完成同样的检查(relational.py:682,1378,应用于 persistence/tags.py:178),且两处构造的资源引用不同(适配器用最新 as_ref(),运行时写死 revision=1),存在检查与使用时间差。
  • dashboard/api.py:101-118 引用 app.py 的私有函数(_path_artifact_read_access、_access_audit_context),在路由依赖之外重新执行鉴权。

后果:绕过 HTTP 的调用方(worker、本地模式)得不到这些规则,违反 AGENTS.md 中“传输与宿主适配器只做翻译”的约定。

A4. 各 Artifact 族的特殊处理分散在共享服务中

  • review/service.py 约有 41 处按族分支,例如 :501、:1024-1025 的硬编码族集合,_validate_proposal_family(:1195-1206)中的类型表。
  • 同类知识在 dream/service.py(约 18 处,含 :448-457 的操作到族映射)、evidence/resolver.py(约 22 处)、dream/bindings.py(约 18 处)重复出现。
  • 新增一种族需要修改 4 个服务。RFC 1549 已提出族注册表,Review 尚未使用。
  • catalog_changes/service.py 另外实现了一套 propose、revise、approve-retry 与 CAS 逻辑,并借用 Review 的错误与审计模型(:43-49),D5 这类修复需要在两处同时进行。
  • Dream 来源通过 ID 前缀判断:candidate_id.startswith("cand_dream_")(review/service.py:195,349,526,542,656,745)。公开 API 不允许传入 candidate_id,因此无法伪造,但候选已有带类型的 audit.dream_run_id 可用。

A5. 服务端路由参数与合约可能漂移

  • 对外提供的 OpenAPI 是规范文档(app.py:1551-1561),FastAPI 自动推导的 schema 被丢弃,因此手写的 Path / Query 约束(如 :2258-2259、:2517-2520、:2625,以及 _list_dreams_query 中无类型的 status / operation)不会与规范比对。
  • 未知查询参数的处理方式不统一(这是安全缺陷 S1 的成因)。
  • 具名解析器按请求查找(app.py:4741-4744),规范中拼错的解析器名要到运行时才报错。
  • 访问控制开启时,格式错误的请求体在鉴权依赖中失败,返回不带详情的 422 invalid_access_request(:4716-4722、:5634);关闭时同样的请求体返回带字段错误的 422 invalid_request(:1375-1394)。生产环境下 agent 与 MCP 客户端拿到的错误信息更少。
  • 少量内部信息回显:AtomicMemoryIndexError 的文本(如迁移问题)被原样返回(app.py:5823-5824)。

A6. 宿主集成缺少统一的行为契约

  • integrations/capabilities.toml 只是能力标签、工具名与证据路径的事实来源(scripts/integration_manifest.py),不描述 hook 行为(查询截断、密钥策略、捕获幂等)。
  • 宿主列表分散在四处:manifest 的 agent_host 条目、setup_app 命令、HOST_GUIDANCE、FIRST_CLASS_HOSTS(cli/hosts.py:44)。FIRST_CLASS_HOSTS 遗漏了 manifest 中标记为完整能力宿主的 zcode 以及 kimi-code,因此 setup select 与 doctor integrations 不显示它们。
  • verify_host 对 claude-code 与 openclaw 跳过安装后检查;Claude Code 的回滚只捕获 SetupError(cli/system.py:1489)。
  • hook 运行时各不相同:Claude Code 使用仅依赖标准库的 python3,Codex 每次 hook 执行 uv run --project,ZCode 与 Kimi Code 使用 Node。

A7. 客户端与服务管理

  • PowerContextClient 只有异步接口且没有重试,CLI 用 asyncio.run 包装每个调用(71 处)。
  • 只有 401、403、503 有专门的错误类型;/dream 的 429 以通用 ServerResponseError 返回,调用方不显式传 response_headers 时 Retry-After 丢失。
  • _prepare_request 中有专门针对 PREPARE_CONTEXT 的特殊分支(client.py:1376-1386)。
  • LangChain 的同步路径在事件循环内被调用时完全跳过召回(middleware.py:184-197);integrations/langchain/.../client.py 与 integrations/langgraph/.../client.py 几乎重复。
  • client/receiver_service.py 为 Skill Receiver 单独写了一套 systemd 用户单元管理,不复用 service/adapters/*,没有所有权与挂起处理,只支持 Linux,enable --now 失败时残留单元文件。它与 adapters/systemd.py 都未涉及 loginctl enable-linger,在无图形界面或仅 SSH 的主机上,用户服务可能在登出后停止、开机不启动(推断)。

A8. 测试与 CI

  • 测试 161k 行,源码 131k 行(含生成代码 152k 行)。
  • 54 个测试文件 monkeypatch 私有属性,22 个引用私有名称,例如 codex_plugin/test_recall.py 调用 _post_json 并对冻结的 settings 使用 object.__setattr__,与 AGENTS.md “通过公开接口断言” 的要求不符。
  • 24 处断言依赖真实耗时(如 -n xdist 下 elapsed < 0.5 对比 0.1 秒截止时间),与 fix(zcode): stop two plugin tests from depending on runner timing #2051 修复过的不稳定问题同类。
  • 根 conftest.py 只清理 POWERCONTEXT_{SERVER,CLIENT,CODEX}_ 环境变量,其余依赖各插件自己的 conftest。
  • tox 与 CI 总是安装全部 extras,单一安装角色下的依赖缺陷(D1、D2)无法被发现。

A9. 其他

  • code 模块独立性:builtin/code 使用子进程中的 tree-sitter 解析器、禁用 hook 的 git 子进程,以及自有的 SQLite 与文件缓存,查询通过 asyncio.to_thread 同步执行(code/application.py:35)。它内部自洽(77 个内部导入,不依赖 artifacts),更像一个独立的产品域。解析器内存限制只在 macOS 上生效(code/process.py:30-36 在其他平台返回 None),Linux 上只有截止时间约束。
  • 推理用量统计:用量只在成功时记录(inference/usage.py:85-110,与 RFC 0072 一致),失败尝试消耗的 token 不进入统计;未发现生成与提取的按 Scope 花费上限(推断)。
  • 小规模问题:
    • server/app.py:1777 为判断某个 Handoff revision 是否存在,载入了所有 revision 的完整内容。
    • 核心 Sources.list()(context.py)映射到无上限的 SourceRepository.list(limit=None)(relational.py:1537),内部未调用,但属于公开 SDK 接口。
  • 未验证的安全推断:
    • loopback 模式默认 access.mode="disabled"(settings.py:156),REST 路由没有 Host / Origin 校验,DNS 重绑定的网页可能读写记忆;FastMCP 的 Host / Origin 防护只覆盖 /mcp。
    • _require_secure_remote_transport(app.py:3915-3923)把任何 loopback 对端视为安全;位于同一主机的明文 HTTP 反向代理之后,远程目标凭据可能经 HTTP 传输。

环境信息

  • 评审对象:master 分支,提交 e4efb39b(ci: scope PR validation to changed areas (#2072))
  • 上述均为非阻塞设计建议,可拆分讨论

Activity

  1. ctxloop commented on Nov 4, 2026

    @ctxloop
    Contributor

    基于最新 master 的复核(1190dcbb)

    Note

    本评论由 Claude Code(Claude Opus 5.5,xhigh)生成,未经人工验证。N2、N3 经实际执行复现,其余结论来自静态读码或推断,未运行完整测试套件。

    以 master 最新提交 1190dcbb 逐条复核本 issue,该提交比评审基线 e4efb39b 新 389 个提交。D1–D6(#2083)、R1–R14(#2084)、#2081 和 #2082 均已关闭,本 issue 的设计问题多数仍成立,其中 A1、A2 有所加重。另有若干新发现,列在文末,建议拆成独立 issue。

    证据标注:读码表示按代码路径确认,复现表示实际执行确认,推断表示未执行验证。行号均基于 1190dcbb,路径省略 src/powercontext/ 前缀。

    现状总览

    条目 现状 要点
    A1 双向依赖 成立,加重 builtin → server 的导入从 13 处增至 22 处,D1 靠打包层修复,依赖方向未变
    A2 中心文件过大 成立,加重 app.py 6644 行、137 个操作;application.py 4954 行;只有 cli/system.py 缩小
    A3 传输层承载领域规则 成立 原有例子全部仍在 HTTP 层,并新增按族的 owner 建立规则
    A4 族特殊处理分散 成立 各服务的族分支基本未变;#2123 的同一修复在 6 个族分支中各写了一遍
    A5 路由与合约漂移 部分成立 S1 成因已修复;其余 4 项仍成立,另有一处新漂移
    A6 宿主行为契约 部分成立 宿主列表与安装后检查已统一;hook 行为仍无契约
    A7 客户端与服务管理 成立 各项均未变化,langchain / langgraph 已开始漂移
    A8 测试与 CI 部分成立 新增按安装角色隔离的 CI,但只做冒烟检查;私有属性 monkeypatch 和计时断言增加
    A9 其他 部分成立 失败用量已记录;DNS 重绑定问题比原推断范围更大
    A1–A9 逐条明细

    A1 双向依赖:成立,加重

    • 导入数量: builtin → server 的导入从 13 处增至 22 处,其中模块级从 3 处增至 6 处。新增的模块级导入有:
      • builtin/runtime/artifact_reads.py:36
      • builtin/runtime/custom_artifact_search.py:66
      • builtin/persistence/migrations/atomic_memory_v1.py:97-98:持久化层直接依赖 server.authz.repository。
    • 依赖 server 的具体实现类: builtin 内出现 type(access.provider) is BuiltinAuthorizationProvider 一类判断,见 builtin/runtime/artifact_reads.py:63、custom_artifact_search.py:296、atomic_memory_processing.py:400,826。
    • 私有访问与无类型字段未变: builtin/runtime/atomic_memory.py:211 仍读取 context.access._deferred_decisions;artifacts/search.py:61-63 中 principal、access、audit 仍为 Any。
    • D1 的修复方式: fix: restore isolated installs and stabilize migration CI #2118 把 server.authz 的 provider 改为延迟加载,pycasbin 移入 builtin extra。这是打包层修复,依赖方向没有调整,也没有抽出独立的授权包。
    • 新增核心包 → builtin 的依赖: artifacts/indexes.py:23 在模块级导入 builtin.artifacts.memory.models,见下文 N3。
    • 领域内部的循环未变:
      • Profile ↔ Review:artifacts/profile/review.py 自基线未改,使用 ReviewService 的 6 个私有成员,共 35 处引用。
      • Dream 直接 UPDATE CANDIDATE_HEADS_TABLE:builtin/dream/service.py:626-634。
      • Prompt 包、Experience ↔ Work 的互相引用仍在。
      • builtin/code/cli.py:27 仍导入 server.configuration。

    A2 中心文件过大:成立,加重

    指标 e4efb39b 1190dcbb
    server/app.py 行数 5965 6644
    create_app 注册的操作 123 137
    builtin/runtime/application.py 行数 4278 4954
    BuiltinRuntime.__init__ 关键字参数 60(AST 计数) 68
    self._runtime._ 私有访问 231 259
    ScopedContextApplication 类体行数 715 1104
    open_builtin_runtime 参数 34 40
    builtin/runtime/composition.py 行数 2156 2517
    client/cli.py 行数 1998 2354
    server/authz/service.py 行数 1797 2067
    cli/system.py 行数 2618 1560
    • cli/system.py 缩小来自 2da90ac2(refactor(cli): unify agent setup through thin native adapters)。
    • 路由仍然只拆出了 Atomic Memory。新增的 server/artifact_plugins.py 和 server/ownership.py 都不含路由,插件和 ownership 路由仍定义在 app.py。
    • 自基线以来,composition.py 被修改 21 次,application.py 19 次,app.py 15 次。

    A3 传输层承载领域规则:成立

    • 原文列出的例子都还在 HTTP 层。acknowledge_handoff(server/app.py:3657-3690)未作修改。
    • Atomic memory 标签写入预鉴权仍跨两个事务:
      • HTTP 侧在 server/app.py:5051-5059 读取 atomic._application,使用最新的 artifact.as_ref()。
      • runtime 侧在 builtin/runtime/relational.py:1521-1528 写死 revision=1。
    • 新增 owner 建立规则,同样放在 HTTP 层: server/app.py:4652-4708 以 on_commit 回调实现,按族决定是否建立 owner,例如 atomic-memory 和 topic-memory 不建立。fix(authz): commit local write ownership atomically #2164 让 owner 写入进入了 runtime 的写事务,原子性有改善,但规则本身仍属于 HTTP 层。
    • 绕过 HTTP 的调用方受影响,已有实例: source_management/gateway.py:286 直接调用 runtime 写入 Source。为补上这项校验,它反向导入了 server.app._reject_reserved_handoff_receipt_content。
    • approve_candidate 的终态重试重复(推断): HTTP 层的重试与 ReviewService.approve(builtin/review/service.py:658-674)已有的幂等重试重复,推断是冗余路径。

    A4 族特殊处理分散:成立

    • builtin/dream/service.py、builtin/dream/bindings.py 自基线未改。builtin/evidence/resolver.py 只有 7 行性能改动。builtin/review/service.py 的族分支多了一行。
    • 族注册表仍未落地:
    • startswith("cand_dream_") 从 6 处增至 7 处,audit.dream_run_id 仍只在一处使用。
    • 同一修复要改多处: fix(review): short-circuit approval of rejected candidates #2123 在 review/service.py 的 6 个按族分支中各加了一次相同的 REJECTED 检查。catalog_changes 没有同步这项修复(见 N9)。

    A5 路由与合约漂移:部分成立

    • 已解决(S1 的成因): _authorization_payload 改为路径值优先(server/app.py:5304)。规范中带查询参数的 5 个操作现在都拒绝未知参数,实现见 _require_strict_query(server/app.py:2906)以及 list_scopes、list_sources 的内联校验。
    • 仍成立:
      • 手写的 Path / Query 约束不与规范比对。tests/test_api_contract.py 只断言 app.openapi() 等于 YAML。
      • 具名解析器按请求查找(server/app.py:5336-5338),注册路由时不校验名称是否存在。
      • 开启访问控制时,格式错误的请求体返回 422 invalid_access_request,不带 details,且这个错误码不在规范中。关闭时返回 422 invalid_request,带字段错误。
      • AtomicMemoryIndexError 的文本仍原样返回给客户端(server/app.py:6475-6476)。
    • 新的漂移实例: 规范声明的 4 个解析器名没有实现,见 N7。

    A6 宿主行为契约:部分成立

    • 已解决:
      • FIRST_CLASS_HOSTS 已删除。CLI 统一使用 installers(),tests/test_integration_manifest.py 断言它与 manifest 的 agent_host 一致,zcode 和 kimi-code 现在都可见。
      • verify_host 已删除,安装后每个宿主都会执行 doctor 检查。
    • 仍成立:
      • manifest(schema v2)不描述 hook 行为。8192 截断上限在各宿主里分别硬编码。
      • HOST_GUIDANCE(cli/guidance.py)仍单独维护。
      • hook 运行时仍混用 python3、uv run 和 node。
    • 行为变化: Claude Code 安装从「只捕获 SetupError 的回滚」变成了不回滚,见 N10。

    A7 客户端与服务管理:成立

    • PowerContextClient 仍只有异步接口、没有重试。client/cli.py 中 asyncio.run 从 35 处增至 47 处。
    • /dream 的 429 仍返回通用 ServerResponseError。create_dream_run(client/client.py:643)不读取 Retry-After。
    • _prepare_request 对 PREPARE_CONTEXT 的特殊处理增至 4 处字段剔除(client/client.py:1426-1449)。
    • 在运行中的事件循环里,LangChain 同步路径仍跳过召回(integrations/langchain/src/powercontext_langchain/middleware.py:187-200)。
    • langchain 与 langgraph 的 client.py、settings.py、scope.py 仍近似重复,并已出现漂移:langgraph 的 settings.py 缺少 hide_input_in_errors=True。
    • client/receiver_service.py 自基线未改。

    A8 测试与 CI:部分成立

    • 已改善: fix: restore isolated installs and stabilize migration CI #2118 新增了按安装角色隔离的 CI job(.github/workflows/master.yml:286-337),覆盖 core、builtin、cli、builtin+cli、server+cli 五种角色,能发现 D1 这类问题。
    • 仍不足: 这个 job 只跑冒烟脚本,不跑测试。core 角色刻意跳过了 artifact_plugins 的导入(scripts/ci_role_smoke.py:111),所以漏掉了 N3。
    • 仍成立:
      • monkeypatch 私有属性的测试文件从 69 个增至 96 个。
      • 依赖真实耗时的断言从 29 处增至 32 处,例如 tests/codex_plugin/test_recall.py:1005 仍是 elapsed < 0.5。
      • tests/conftest.py:60 仍只清理三个环境变量前缀。

    A9 其他:部分成立

    • 已解决: 失败尝试中已知的用量现在也会记录(feat(inference): enforce final request and response privacy checks #2311,builtin/inference/usage.py:173-213)。
    • 仍成立:
      • code 模块仍独立。
      • 没有按 Scope 累计的花费上限(推断)。
      • 判断 Handoff revision 是否存在时,仍载入全部 revision(server/app.py:2012 → builtin/persistence/artifacts.py:677-703)。
      • Sources.list() 最终仍以 limit=None 查询。
    • 安全推断更新:
      • REST 仍没有 Host / Origin 校验,/mcp 默认同样没有,见 N4。
      • _require_secure_remote_transport(server/app.py:4432-4440)的风险有前提。uvicorn 默认信任来自 127.0.0.1 的 X-Forwarded-*,同机代理发送这些头时不会误判;只有代理不发送时风险才成立。

    对原文的更正

    • A1: artifacts/profile/review.py 使用 ReviewService 的 6 个私有成员,共 35 处引用,不是约 10 个。如果把 _candidates、_artifacts 上被调用的方法也算进去,约为 10 个。
    • A2: server/app.py 中的 Protocol 端口是 27 个,不是 40 个,基线也是 27 个。
    • A7 asyncio.run: 基线 src 中 asyncio.run 共 46 处,复现不出「71 处」。
    • A7 receiver: client/receiver_service.py 有 _MANAGED_HEADER 所有权检查。
    • A7 linger: 不启用 linger 是 RFC 1299 的明确取舍(docs/en/rfcs/1299_local_server_availability_and_service_installation.md:268)。但两条服务路径都没有检查或提示 linger 状态。
    • A9: 解析器内存限制并非只在 macOS 生效。Linux 上 builtin/code/worker.py:31-32 通过 RLIMIT_AS 设置硬限制,macOS 上由父进程采样 RSS。基线即是如此。

    新发现(建议拆分为独立 issue)

    N1【高】未认证的慢速请求可占满并发名额,使全部请求返回 503(包括 /health)

    • 位置: server/factory.py:451-455、server/http_limits.py:35-92,由 feat(server): bound HTTP request bodies and active responses #2162 引入。
    • 原因: HttpResourceLimitsMiddleware 在认证中间件之后通过 add_middleware 注册,处在最外层。请求先占用一个并发名额,再读取请求体,读取过程没有超时。
    • 场景:
      1. 不带凭据建立 64 个连接,即 max_concurrent_requests 的默认值。
      2. 每个连接声明 Content-Length 后,慢速发送请求体。
      3. 名额耗尽后,所有请求都返回 server_overloaded。存活探针失败还可能触发反复重启。
    • 附带问题: 未认证请求的请求体也会被缓冲,最坏约 64 × 16 MiB。此前认证中间件只看请求头就能拒绝这类请求。
    • 证据: 读码。「uvicorn 默认对进行中的请求体读取没有超时」未实测。
    • 方向:
      • 认证放到准入之前,或者至少不缓冲未认证请求的请求体。
      • 读取请求体时加超时或最低速率。
      • health 端点不占用名额。

    N2【中高】含 Claude Code / Codex 采集 prompt 的 Scope 无法 archive export

    • 位置: builtin/portability/bundle.py:140(_ABSOLUTE_HOST_PATH)、:1833-1848(_validate_safe_value)。
    • 场景:
      1. Claude Code hook 默认开启 capture_prompts,会在 Source metadata 中写入 "cwd": "<绝对路径>"(integrations/claude-code/plugins/powercontext/hooks/user_prompt_submit.py:259)。Codex 会话导入同样会写入(client/session_import.py:581)。
      2. 导出时会递归检查 payload。键名为 cwd 且值为绝对路径时,抛出 BundleFormatError("bundle contains an absolute host path")。
      3. 整个导出失败,没有排除或脱敏选项。
    • 证据: 复现。单独执行 _validate_record_safe,输入为 Claude Code prompt 的 payload 形态。
    • 方向:
      • 路径检查只作用于主机本地配置字段;或者提供脱敏、排除机制。
      • 补充回归测试:含 metadata.cwd 的 Source 能完成导出与恢复。

    N3【中】只安装核心包时,powercontext.artifact_plugins 和 from powercontext import * 失败

    • 原因: artifact_plugins 列在 powercontext.__all__ 中(__init__.py:126,135)。导入链为:artifact_plugins.py:27 → artifacts/indexes.py:23 → builtin/artifacts/memory/__init__ → jev_reranking.py:25(import httpx)。
    • 证据: 复现。在干净的 venv 中执行 pip install .(不带 extras)后,以下两条语句都抛出 ModuleNotFoundError: No module named 'httpx':
      • import powercontext.artifact_plugins
      • from powercontext import *
    • CI 未发现的原因: scripts/ci_role_smoke.py:111 只在安装了 extras 时才导入 artifact_plugins。
    • 方向:
      • 让 artifacts/indexes.py 不再依赖 builtin,例如把 EmbeddingProfile 移入核心包;或者从核心 __all__ 中移除,并在文档中声明需要的 extra。
      • smoke 检查覆盖 core 角色的全部公开导出。

    N4【中】/mcp 默认没有 Host / Origin(DNS 重绑定)防护

    • 原因: server/mcp.py:616 调用 http_app(path="/", stateless_http=True) 时没有开启 host/origin 防护,而两个上游库的默认值都是关闭:

      • fastmcp 3.4.10 的 http_host_origin_protection 默认为 False。
      • MCP SDK 1.28.1 的 enable_dns_rebinding_protection 默认为 False。

      仓库中也没有设置 FASTMCP_HTTP_HOST_ORIGIN_PROTECTION。

    • 影响: loopback 模式默认 access.mode="disabled"(server/settings.py:195),REST 又没有 Host 校验。因此 A9 中关于 DNS 重绑定的推断同样适用于 /mcp。

    • 证据: 读码,包括上游源码。可利用性未实测。

    N5【中,多节点】索引校验 token 会被其他节点清除,搜索可能长期不可用

    • 原因:
      • builtin/persistence/artifact_indexes.py:708-714 在 build() 开头无条件把 validation_token 置为 None,无法区分崩溃遗留的 token 和其他节点正在使用的 token。
      • 每个 Server 进程都会启动 _maintain_search(server/artifact_plugins.py:166),没有租约,也没有选主。
    • 场景:
      1. 多节点 OceanBase 部署中,节点 A 正在校验。
      2. 节点 B 清除了 A 的 token,换成自己的。
      3. A 提交时发现 token 不符,放弃本轮;下一轮又清掉 B 的 token。如此循环往复。
      4. 校验耗时长于轮询周期时,该族搜索一直返回 index rebuild is incomplete,并且每轮都重复全量校验。
    • 另外(推断):
      • 最终提交要求校验期间 change_seq 不变(:854-874),持续有写入的族可能始终无法发布。
      • _coverage_valid 逐个 head 读取,并把全部投影载入内存。
    • 证据: 读码,未在多节点环境实测。

    N6【中,外部 owner 存储】不同主体写 Profile 时,flush 可能返回 409 并回滚

    • 原因: server/authz/owner_recovery.py:548 生成 operation_id 时只用 deployment、kind 和资源键,不含 principal。
    • 场景:
      1. 主体 S 的 flush 已提交 owner intent,但外部投递尚未完成,或者外部存储故障。
      2. 主体 U 的 flush 从外部存储读到「无 owner」(server/app.py:2795-2801、server/factory.py:684-691),以 owner=U 再次 stage。
      3. _require_same_intent(:531-537)抛出 AccessConflictError,U 的 flush 整体回滚。
    • 影响: 外部存储故障期间,所有非 S 发起的 profile 写入都会失败。同库模式不受影响。
    • 证据: 读码。
    • 方向: stage 之前先查本地 intent。已有相同 operation_id 时,视为 owner 已存在,或者先 replay 这条 intent。

    N7【低】规范声明的 4 个解析器名没有实现,x-powercontext-access 对这些操作不生效

    • 现状: 以下 4 个名字在 openapi/powercontext.yaml 中声明,但不在 _NAMED_ACCESS_RESOLVERS(server/app.py:5747)中:
      • source_status_access
      • source_instances_access
      • source_management_channel_access
      • source_instance_channel_access
    • 原因: 对应路由由 source_management/gateway.py:388-440 通过 add_api_route 直接挂载,鉴权在 gateway 中手写。
    • 评估: 目前没有发现手写鉴权有误,但这是 A5 所述漂移的一个实例。

    N8【待确认语义】从 auth.administrators 移除用户后,已持久化的 server.admin 不会被撤销

    • 现状: server/authz/composition.py:136-161(_bootstrap_server_roles)只创建绑定,没有对账或撤销逻辑。
    • 待确认: 二选一:
      • 「只授予、不回收」:需要在文档中说明,并提供撤销方式。
      • 启动时按配置对账,撤销不在列表中的绑定。

    其他低优先级

    • N9:catalog_changes 没有跟进 fix(review): short-circuit approval of rejected candidates #2123(推断)。 _approve_once(builtin/catalog_changes/service.py:289-300)先锁定目标、校验证据和 basis,lock_pending 才报终态错误。批准一个已驳回的 Tag 候选时,可能先返回证据或 basis 错误,而不是终态错误。
    • N10:Claude Code 安装失败后不回滚(读码)。 在 ClaudeInstaller.install(cli/system.py:1486-1511)中,marketplace add 成功、plugin install 失败后,marketplace 会保留。之后换 --source 重跑会报来源不一致。
    • N11:长 prompt 的截断方向不一致(读码)。
      • 宿主 hook 保留末尾 8192 个字符或字节,例如 integrations/claude-code/plugins/powercontext/hooks/prepared_context.py:85。
      • LangChain、LangGraph 和 pydantic-ai 保留开头,例如 integrations/langchain/src/powercontext_langchain/middleware.py:140。
      • 同一段超长 prompt 在不同集成中召回的内容不同。
    • N12:未认证调用方可以枚举 source instance(读码)。 source_management/manager.py:102-108 先查实例:实例不存在返回 404,凭据错误返回 401。
    • N13:ownership migration 为 Candidate 指派 owner 时没有逐项审计(读码)。 调用链为 server/ownership.py:383 → server/authz/service.py:1345-1378。
    • N14:一条损坏的 payload 会让整页列表报错(读码)。 owner_recovery.pending()(:172)逐行解码整页,任何一条损坏都会让整页报错,且错误中不带 operation_id。
    已核查、未发现问题的范围
  2. dobet commented on Nov 4, 2026

    @dobet
    Author

    Adding a few points not covered by the previous re-check on 1190dcbb:

    A5: Dream routes still don't constrain scope_id / run_id; a blank scope returns 500 (reproduced)

    Items with open PRs

    Item PR Scope
    A1 #2244 refactor!: restore package layering in src, open
    A8 #2397 test: remove redundant tests and implementation detail checks, open; targets tests that depend on implementation details
    A6 / A7 #2318 refactor(integrations): generate the shared Python plugin helpers from one source, open; targets duplicated plugin helpers across hosts

    A6: hook runtimes

    Besides python3, uv run, and Node, there are two more hook runtimes:

    • WorkBuddy uses POWERCONTEXT_PYTHON.
    • OpenCode, Pi, and DSH are in-process TypeScript plugins.

    That makes 6 in total.

  3. ctxloop commented on Nov 6, 2026

    @ctxloop
    Contributor

    Status

    Checked on 2026-11-08 against master ee3c1ee3. The original review baseline is e4efb39b, and the re-check baseline is 1190dcbb. This comment is edited in place as PRs open and merge.

    ✅ resolved · 🔶 partly resolved · 🟡 in review · ⬜ not started

    Item Status PR Remaining scope
    A1. Bidirectional dependencies ⬜ Open #2244 restores package layering in src.
    A2. Oversized central files ⬜
    A3. Domain rules in the transport layer ⬜
    A4. Family-specific handling in shared services ⬜
    A5. Route parameter and contract drift 🔶 #2494 (e0bc7f8c), #2502 (89d960e3) Blank scope_id is rejected on the Dream (#2494) and Tag and Atomic Memory state (#2502, #2493) routes. Remaining: hand-written Path / Query constraints are not compared with the OpenAPI contract, unknown query parameters are handled inconsistently, resolver names are checked only at runtime, the authentication-dependency 422 has no details, and AtomicMemoryIndexError text is returned as is.
    A6. Host behavior contract ⬜ Merged #2318 (73a819df) generates the shared Python plugin helpers from one source, as a first step of #2250.
    A7. Client and service management ⬜
    A8. Tests and CI ⬜ Merged #2397 (24e0017e) removes redundant tests and implementation-detail checks.
    A9. Other ⬜ DNS rebinding is tracked under N4.
    N1. Unauthenticated slow requests exhaust concurrency slots 🔶 #2512 (ee3c1ee3) #2512 authenticates protected requests before reading their bodies. Authentication-exempt paths still read the full body before the app runs: #2547.
    N2. Scopes with metadata.cwd cannot be exported ⬜
    N3. artifact_plugins import fails with the core package only ⬜
    N4. No Host / Origin protection on /mcp and REST ⬜
    N5. Index validation tokens cleared by other nodes ⬜
    N6. Profile flush 409 with an external owner store ⬜
    N7. Resolver names declared in the contract are not implemented ⬜
    N8. Removing an administrator does not revoke server.admin ⬜
    N9. catalog_changes does not follow #2123 ⬜
    N10. Claude Code installation does not roll back on failure ⬜
    N11. Inconsistent long-prompt truncation direction ⬜
    N12. Unauthenticated enumeration of source instances ⬜
    N13. No per-item audit when ownership migration assigns owners ⬜
    N14. One corrupt payload fails a whole page ⬜
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions