Skip to content

fix(wiki-pub): 创建发布唯一约束冲突 500 修复(双层防御 + 409 SSOT) - #418

Merged
ThreeFish-AI merged 2 commits into
feature/1.x.xfrom
ThreeFish-AI/wiki-pub-conflict
Apr 26, 2026
Merged

fix(wiki-pub): 创建发布唯一约束冲突 500 修复(双层防御 + 409 SSOT)#418
ThreeFish-AI merged 2 commits into
feature/1.x.xfrom
ThreeFish-AI/wiki-pub-conflict

Conversation

@ThreeFish-AI

Copy link
Copy Markdown
Owner

背景

  • 本次变更要解决的问题:Wiki 页「新建 Wiki 发布」对话框点击「创建」时,对已存在 LIVE 发布的 Catalog 二次提交 → POST /api/knowledge/wiki/publications 返回 500 Internal Server Error(前端 toast 仅显示「Failed to create wiki publication: Internal Server Error」)。根因为 endpoint 仅捕获 ValueError,未覆盖 sqlalchemy.exc.IntegrityError——DB 部分唯一索引 uq_wiki_pub_catalog_active(Phase A WHERE publish_mode='LIVE':每 catalog 仅允许 1 个 LIVE)违反时直接漏出 ASGI 中间件栈,前端拿不到任何排障线索。
  • 关联上下文docs/issue.md ISSUE-024(与 ISSUE-016 同 endpoint 不同根因——「错误处理缺失」结构性问题);约束定义见 migrations/0007_catalog_singleton_phase_a.py

核心变更

  • 后端 endpoint 双层防御apps/negentropy/src/negentropy/knowledge/api.py:(a) 业务前置检查:调用 service 前对同 catalog LIVE 发布 / 同 (catalog,slug) 做轻量 select(...).limit(1),命中即抛 409 + WIKI_PUB_CATALOG_LIVE_CONFLICT / WIKI_PUB_SLUG_CONFLICT,details 携带既有发布 id/name/slug 引导用户跳转编辑或归档;slug 归一化复用 negentropy.knowledge.slug.slugify。(b) IntegrityError 兜底:包裹 db.commit(),按 exc.orig 字符串中约束名映射回 409 code,覆盖竞态与未来新增约束;错误体 {code, message, details} 与既有 _map_exception_to_http 同形,message 中文面向用户。
  • 前端透传apps/negentropy-ui/features/knowledge/utils/knowledge-api.tscreateWikiPublication 改为复用 handleKnowledgeError<T>parseKnowledgeError 解析后端 {code, message, details}toast.error(err.message) 自然显示中文友好提示,CreateWikiPublicationDialog UI 零改动。
  • 集成测试tests/integration_tests/knowledge/test_wiki_publish_modes.py:新增 TestCreateWikiPublicationApiConflict 三例(LIVE 冲突 / SLUG 冲突 / CATALOG_NOT_FOUND 回归)+ isolated_wiki_catalog fixture 用 test-wiki-pub-conflict-<uuid> 派生独立 app_name 规避 dev DB 的 uq_doc_catalogs_app_singleton 约束。
  • 文档沉淀CHANGELOG.md [Unreleased] / Fixed + docs/issue.md ISSUE-024:根因、五点修复手法、三条后续防范、同型扫荡指引。

风险与回滚

  • 主要风险:(1) 业务前置检查与 IntegrityError 兜底两条路径在错误体结构上对齐,但若新增第三方约束,未列入字符串映射的会落入 WIKI_PUB_CONFLICT 通用 code(仍 409,体验降级而非 500);(2) 新增两次 select(...).limit(1) 查询带来微量额外开销,已通过 .limit(1) 控制;(3) 前端从 res.statusText 改为 handleKnowledgeError,对响应体非 JSON 的极端场景由 parseKnowledgeError 内部 try/catch 兜底为 UNKNOWN_ERROR,不抛 SyntaxError。
  • 回滚方式:直接 git revert 本 PR;不涉及 schema / migration 变更;前后端解耦修改可单独回退(虽不建议拆分以免出现「后端 409 但前端读 statusText」中间态)。

验证证据

  • 集成测试uv run pytest tests/integration_tests/knowledge/test_wiki_publish_modes.py::TestCreateWikiPublicationApiConflict --no-cov -v3 passed in 2.91s(同一 catalog 二次创建 → 409 LIVE_CONFLICT、slug 重复 → 409 SLUG_CONFLICT、catalog 不存在 → 404 回归)。
  • 静态检查uv run ruff check apps/negentropy/src/negentropy/knowledge/api.py apps/negentropy/tests/integration_tests/knowledge/test_wiki_publish_modes.py → All checks passed;pre-commit hooks(ruff lint / ruff format / ESLint)全绿。
  • 预先存在失败排查:同文件其他 11 例在 dev DB 上的失败为 fixture app_name='negentropy' 与共享 dev DB 的 uq_doc_catalogs_app_singleton 冲突(ISSUE-015 单实例 Catalog 收敛副作用),通过 git stash 已验证与本次修复无关。

影响范围

  • 前端apps/negentropy-ui/features/knowledge/utils/knowledge-api.ts::createWikiPublication 错误处理复用既有 handleKnowledgeErrorCreateWikiPublicationDialog.tsx 零改动;BFF _proxy.ts 零改动。
  • 后端apps/negentropy/src/negentropy/knowledge/api.py::create_wiki_publication 双层防御;service / DAO / ORM 模型 / migration 零改动;AsyncSessionLocal 路径与 logger 风格沿用。
  • GitHub Actions / 文档:CI 配置零改动;CHANGELOG.md + docs/issue.md ISSUE-024 同步沉淀。

Next Best Action

  • 同型扫荡审计(建议作为 follow-up):对 apps/negentropy/src/negentropy/{knowledge,interface,memory,auth}/api.py 全量 db.add(...) + db.commit() 路径做一次 IntegrityError 覆盖审计;interface/api.py:1336/1393/1496 三处 SubAgent 端点已有正确写法可作为模板。
  • 前端 fetch helper 写作纪律扫荡apps/negentropy-ui/features/*/utils/*-api.ts 全文检索 statusText,将 throw new Error(... statusText ...) 形式逐项改为 handleKnowledgeError<T> 统一路径。

根因:POST /api/knowledge/wiki/publications 仅捕获 ValueError,未覆盖
sqlalchemy.exc.IntegrityError——uq_wiki_pub_catalog_active 部分唯一索引
(每 catalog 仅允许 1 个 LIVE 发布)违反时直接漏出为 500。

修复:
- 后端 api.py: (a) 业务前置检查命中即抛 409 + WIKI_PUB_CATALOG_LIVE_CONFLICT
  / WIKI_PUB_SLUG_CONFLICT,details 引导用户跳转编辑或归档;(b) 包裹
  db.commit() 兜底 IntegrityError 覆盖竞态;错误体 {code, message, details}
  与 _map_exception_to_http 同形,message 中文。
- 前端 knowledge-api.ts: createWikiPublication 改用 handleKnowledgeError 透传
  后端 message,dialog UI 零改动。
- 测试 test_wiki_publish_modes.py: 新增 TestCreateWikiPublicationApiConflict
  三例 + isolated_wiki_catalog fixture 规避 dev DB uq_doc_catalogs_app_singleton
  约束冲突。
- 文档 CHANGELOG.md / docs/issue.md ISSUE-024: 沉淀根因、防范、同型扫荡。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
@ThreeFish-AI
ThreeFish-AI merged commit a20317c into feature/1.x.x Apr 26, 2026
16 checks passed
@ThreeFish-AI
ThreeFish-AI deleted the ThreeFish-AI/wiki-pub-conflict branch April 26, 2026 13:24
ThreeFish-AI added a commit that referenced this pull request Apr 26, 2026
…敛 6 处冲突;

# 根因
PR #416(已合并 2026-04-26)将 feature/1.x.x squash 合入 master 形成单 commit c0bb18fc0bb18f 携带的 6 文件内容是 feature/1.x.x 在 PR #417/#418 之前的旧快照。
此后 feature/1.x.x 持续推进 PR #417(Dependabot 6 项 CVE 收敛)与 PR #418(Wiki Publish 500 双层防御),
对同 6 文件做了演进/超集变更,与 master c0bb18f 形成 3-way merge 冲突。

# 6 文件冲突处置(全部 git checkout --ours 取 feature/1.x.x 版本)
- CHANGELOG.md:保留 feature/1.x.x 顶部 PR #418 修复条目(master c0bb18f 无此条目);
- docs/issue.md:保留 feature/1.x.x 新增 ISSUE-024(Dependabot 6 项 CVE 收敛 / PR #417)+ ISSUE-025(Wiki 发布 500 双层防御 / PR #418)两段长教训沉淀;
- apps/negentropy-ui/features/knowledge/utils/knowledge-api.ts:保留 PR #418 的 handleKnowledgeError<T> 透传(替换 master 端裸 Error(statusText) 丢响应体写法);
- apps/negentropy-wiki/package.json:保留 PR #417 的 pnpm.overrides.postcss>=8.5.10 间接依赖收敛(master c0bb18f 无 overrides 块);
- apps/negentropy/src/negentropy/knowledge/api.py:保留 PR #418 的 IntegrityError 双层防御(前置 select 业务校验 + commit 兜底)与 WIKI_PUB_CATALOG_LIVE_CONFLICT / WIKI_PUB_SLUG_CONFLICT 409 SSOT 错误结构;
- apps/negentropy/tests/integration_tests/knowledge/test_wiki_publish_modes.py:保留 PR #418 新增 TestCreateWikiPublicationApiConflict 三例 + isolated_wiki_catalog fixture。

附带:apps/negentropy-wiki/pnpm-lock.yaml 自动合并后描述符与 feature/1.x.x 不一致(master 端格式更冗长),
已 reset 回 feature/1.x.x 版本以维持 lockfile 与 package.json overrides 一致性。

# 策略合理性
已逐文件 git diff 验证:feature/1.x.x 是 master c0bb18f 内容的严格演进/超集,
不存在 master 独有但 feature/1.x.x 缺失的修复——master 端零回退、feature/1.x.x 端零损伤。
本次 merge commit 树内容与 origin/feature/1.x.x 完全一致,仅引入"已回流 master c0bb18f"的合并性声明,
等价于 -s ours 语义但保留 --no-ff 双亲结构以记录回流事实。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
ThreeFish-AI added a commit that referenced this pull request Apr 26, 2026
…i 发布 500 双层防御; (#419)

* fix(ui-test): 使用 vi.stubEnv 替换 NODE_ENV 直接赋值以修复 typecheck:test TS2540;

@types/node@20.19.35 将 NodeJS.ProcessEnv["NODE_ENV"] 标记为 readonly,
backend-url.test.ts 的三处 `process.env.NODE_ENV = "..."` 触发 TS2540,
导致 CI UI Type Checks / Typecheck tests 步骤退出码 2 并拖垮 Playwright Smoke。

改用 Vitest 原生 vi.stubEnv + vi.unstubAllEnvs:
- 绕过 ProcessEnv readonly 类型约束,类型检查干净通过;
- afterEach 统一 unstub,省去 originalNodeEnv 手工持有与回滚;
- 保持单测运行时行为不变(本地 397 tests 全绿)。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(auth-ping): 禁用 LLM Ping 重试放大并补齐结构化日志;

- _ping_llm 增加 num_retries=0 + max_retries=0,切断 litellm/openai SDK 双层自动重试,避免 1 次点击被 SDK 放大为 3 次上游请求而触发自循环式 429;
- asyncio.wait_for 超时 30s → 300s,兼顾推理模型冷启动与跨境延迟,弥补去掉 SDK 重试后的时间预算真空;
- ping_model 新增入口/成功/失败三处 structlog 事件(model_ping_start / _ok / _failed),携带 vendor、model、api_key_fingerprint、latency_ms、exc_type、exc_status 等字段,便于后续溯因;
- 异常分支补齐 429 (RateLimitError) 分类,超时提示同步为 5 min;
- interceptors 补注释说明故意不静默 openai._base_client / httpx 日志,防止未来误静默导致 Ping 放大类问题再次失观测;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(admin-models-ping): 归一化 Gemini api_base 以抵消 litellm 丢失 /v1beta/ 的拼接缺陷;

- 新增 config/model_resolver.py::normalize_api_base_for_litellm():Google 官方域名映射为 None 放行 litellm 内置 URL,自建代理补齐 /v1beta,非 gemini/ 模型恒等透传;
- 在 _build_llm_kwargs / _build_embedding_kwargs / _ping_llm 三处共享同一规则,Ping 同步注入 drop_params=True 与 _DEFAULT_LLM_KWARGS 对齐;
- 新增 16 个单元测试覆盖归一化规则与 Ping kwargs 不变量;
- CHANGELOG.md 记录根因与修复方式。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(admin-models-ping): 归一化 OpenAI api_base 补齐 /v1 以抵消 litellm 未注入版本段的 SDK 拼接缺陷;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(knowledge-separators): 新增 SeparatorsTextarea 以规避受控 textarea 的 encode(decode()) 非幂等抖动;

Knowledge → Corpus 新建/编辑对话框及 Corpus 详情页 ChunkingStrategyPanel 中 "Separators (one per line)" 文本域键入单个 `\` 会被自动扩写为 `\\`、且无法删除或继续输入 `\n\n`。根因是 4 处 textarea 采用「受控组件 + 非幂等显示变换」反模式,将 `encodeSeparatorsForDisplay(decodeSeparatorsFromInput(input))` 直接作为 value 形成 round-trip,该组合对「孤立反斜杠」这类中间态非幂等。

- 新增 `features/knowledge/components/SeparatorsTextarea.tsx`:以「原始输入字符串」为本地显示状态,采用 React 官方推荐的「渲染期比对上一次 prop」模式(`setState` 同步触发重渲染,替代 useEffect + setState 的级联渲染);仅在外部 `value: string[]` 的语义(经 `separatorsArrayEqual` 判定,而非数组引用)发生变化时才重同步;forwardRef 透传以支持父级 focus。
- `features/knowledge/utils/knowledge-api.ts`:新增 `separatorsArrayEqual` 领域化数组等价函数(供组件与后续复用)。
- 4 处调用点(CorpusFormDialog recursive/hierarchical、base/page.tsx recursive/hierarchical)统一替换为 SeparatorsTextarea。
- 补齐 5 个 RTL 回归用例:孤立 `\`、字面量 `\n`、外部重同步、等值引用稳定、退格清空。
- `tests/helpers/knowledge.ts`:mock harness 补齐 SeparatorsTextarea 与 separatorsArrayEqual 导出,避免 KnowledgeBasePage.test.tsx 的 `vi.mock` 漏接新符号。

后端 schema 与 encode/decode 契约零改动;验证:lint / typecheck / typecheck:test / 全量 402 test 全绿。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(knowledge): 新增 Corpus 级 LLM/Embedding 模型配置与 Admin Model Configs CRUD;

- auth/api.py: 新增 /admin/model-configs CRUD 四端点(GET/POST/PATCH/DELETE),支持 model_type/vendor/enabled 过滤、is_default 互斥、Corpus 引用反查冲突检测
- config/model_resolver: 新增 resolve_llm_config_by_id / resolve_embedding_config_by_id,带 60s 缓存与回退全局默认
- knowledge/embedding: build_embedding_fn / build_batch_embedding_fn 接受 embedding_config_id 参数
- knowledge/service: _attach_embeddings 接收 corpus_config,按 Corpus 级配置构建 embedding fn
- knowledge/extraction: _build_llm_invocation_plan 支持 llm_config_id,从 corpus_config.models 读取
- knowledge/api: _serialize_corpus_config 白名单校验 config.models;update_corpus Embedding 维度变更时自动 enqueue rebuild_source;CorpusResponse.rebuild_triggered 返回重建概要
- knowledge/schemas: CorpusResponse 新增 rebuild_triggered 可选字段

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(knowledge-ui): Admin Models 页 Registered Models CRUD 与 Corpus Settings Models 下拉;

- admin/models/page: Vendor Dialog 内嵌 Registered Models 模块,支持按 model_type 分组展示、Add/Edit/Delete 操作、dimensions badge
- knowledge/base/page: CorpusSettingsPanel 新增 Models Settings 下拉(Embedding/LLM),Embedding 维度变更时弹红色确认 Dialog,保存后展示 rebuild_triggered toast
- knowledge-api: 新增 fetchModelConfigs、ModelConfigItem/CorpusModelsConfig 类型;buildCorpusConfig 接受 models 参数;CorpusRecord 新增 rebuild_triggered
- features/knowledge/index: 导出新增类型与函数

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* test(knowledge): 新增 Model Configs CRUD、Resolver by-id、Corpus Models 校验单元测试;

- test_model_configs_api: 覆盖 admin 权限校验、model_type 验证、_model_config_to_dict 序列化
- test_model_resolver_by_id: 覆盖 None 回退默认、行不存在/已禁用/类型不匹配返回 None
- test_api_corpus_models: 覆盖 config.models 白名单过滤、非法 UUID 拒绝、引用校验 404

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* test(ci): 修复因 PR#372 新增模型配置功能导致的 CI 测试失败;

- Backend: 为 update_corpus 测试补充缺失的 background_tasks 参数;
- UI: 在 knowledge feature mock 中补充 fetchModelConfigs 导出与默认返回值;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* test(ci): 补充集成测试 FakeRepository 缺失的 get_corpus_by_id 方法;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(admin-models): 补齐 Next.js BFF 层 model-configs 代理路由,修复 POST 404;

Admin → Models 页 OpenAI Setting 模态框 Add Model Save 时,
POST /api/auth/admin/model-configs 返回 404。根因为 BFF 层缺少
model-configs 代理路由文件,补齐 GET/POST/PATCH/DELETE 四个方法。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(admin-models): 在 Vendor 卡片底部加入已启用模型折叠披露入口;

- 新增 VendorModelsDisclosure 组件:按 vendor + enabled 过滤模型,并按 LLM/Embedding/Rerank 分组渲染;空分组不渲染,三类全空时组件整体不渲染;
- 抽出 ModelConfigRecord/ModelKind/MODEL_KINDS 至 types/admin-models.ts 作为单一事实源;
- 在 admin/models 页面的 OpenAI/Anthropic/Gemini 卡片内引入折叠披露入口,让管理员无需打开 Setup 对话框即可查看已启用模型全貌;
- 补齐 Vitest 单测:空态、默认收起与总数、展开后分组、enabled/vendor 过滤、Default 与 dimensions 徽章、aria-controls 对应。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* chore(gitignore): 忽略 Playwright MCP 调试产物目录;

Playwright MCP 在会话中会生成 .playwright-mcp/ 存放 console 与页面快照,属于临时调试输出,不应进入版本库。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* chore(mcp-negentropy-perceives): 将预置 MCP Negentropy Perceives 的端口由 8092 迁移为 2992;

- 更新 Alembic 迁移 0002_seed_negentropy_perceives 中预置 seed 的 URL,改为 http://localhost:2992/mcp;
- 同步对齐迁移集成测试中 test_negentropy_perceives_seeded_by_migration 与幂等自愈用例的 URL 断言;
- ON CONFLICT (name) DO UPDATE 原有契约不变,既有部署在下一次 alembic upgrade head 将自愈为新端口。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(interface): 后端 plugins 模块整体更名为 interface,并将 Admin/Models 端点迁入 interface/models_api

- 源码目录 negentropy/plugins/ 整体迁移至 negentropy/interface/,路由前缀 /plugins → /interface;logger 名同步
- 从 auth/api.py 摘出 6 条 Admin Models 路由及相关工具函数,独立为 interface/models_api.py,前缀 /interface/models/*,保留 admin role 校验(_vendor_config_to_dict / _mask_api_key / _sanitize_error / SUPPORTED_VENDOR_CONFIG_VENDORS 随同迁入)
- rbac.PERMISSIONS 新增 interface:read / interface:write;admin 角色透过 interface:* 通配保持访问
- engine/bootstrap.py 更新 import 与 /plugins → /interface 挂载守卫及日志文案
- /interface/stats 新增 models: {total, enabled, vendors} 聚合字段(VendorConfig 计数 + ModelConfigRecord.enabled 计数)
- 测试目录 tests/unit_tests/plugins → tests/unit_tests/interface,test_model_configs_api/test_admin_models_ping 更名并迁入新目录,URL 断言同步切换至 /interface/models/*
- knowledge/extraction.py 的模块引用随命名统一更新

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(interface-ui): 前端 plugins → interface 命名统一,Admin/Models 迁入 Interface 导航并新增 Dashboard 卡片

- UI 路径:/plugins → /interface;/admin/models → /interface/models;config/navigation.ts 主导航 href 同步
- 源码目录:app/plugins → app/interface;app/admin/models 迁入 app/interface/models;components/admin/VendorModelsDisclosure → components/interface/VendorModelsDisclosure;types/admin-models → types/interface-models;components/ui/PluginsNav → components/ui/InterfaceNav
- API 代理:app/api/plugins/* → app/api/interface/*;app/api/auth/admin/{vendor-configs,model-configs,models/ping} 合并迁入 app/api/interface/models/{vendor-configs,configs,ping};stats 代理回源 URL 改为 /interface/stats 且响应类型新增 models 字段
- InterfaceNav NAV_ITEMS 顺序调整为 Dashboard → Models → SubAgents → MCP → Skills;Models 条目基于 useAuth() 对 admin 条件渲染
- Interface Dashboard 新增 Models StatCard 与 Manage Models Quick Link(admin-only),卡片顺序与 Nav 对齐;非 admin 维持 3 卡片布局
- app/interface/models/page.tsx 内置 admin 角色 guard:useEffect 中 !user?.roles?.includes('admin') 触发 router.replace('/interface')
- AdminNav 移除 Models NAV_ITEMS;knowledge-api.ts 中 /api/auth/admin/model-configs 更新为 /api/interface/models/configs;tests/helpers 同步
- 测试迁移:tests/unit/plugins → tests/unit/interface;PluginsPage.test / PluginsNav.test / VendorModelsDisclosure.test 随目录与组件更名;新增 Models 卡片与 Nav 条件渲染测试;mock URL 全面切换至 /api/interface/*

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(interface): 同步文档与 CHANGELOG,记录 Admin/Models → Interface/Models 迁移与 plugins → interface 命名统一

- user-guide.md:主导航表 Interface 路径改为 /interface;目录与 6 章标题改为 "Interface 能力接入";6.1 模块表扩展至 5 行并新增 Models 条目(仅 admin,含 redirect guard 说明);新增 6.6 "Models 管理(仅 admin)",原 7.2 内容(类型/供应商/流程图/操作表)完整迁入;Admin 章节仅保留 Users/Roles 并补充迁移提示;"Admin > Models / Admin → Model 页" 相关表述统一替换为 Interface 上下文
- development.md:项目结构中的 plugins/ 目录注释更新为 interface/(Models / SubAgents / MCP / Skills)
- framework.md:5.8 "Plugin Architecture" 章节更名 "Interface Architecture",代码路径指向 negentropy/interface/;bootstrap 流程图挂载路由改为 /interface;第 9 节目录树将 api/plugins 与 plugins/ 重写为 api/interface 与 interface/(models/subagents/mcp/skills 4 子模块);admin/ 块仅保留 roles;扩展脚手架表 "新插件" 改为 "新能力接入";"插件生态" 术语同步更新
- CHANGELOG.md:新增 Admin/Models → Interface/Models 迁移与命名统一条目;更正 "默认 LLM 模型" 说明指向 Interface → Models

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(model-resolver): 新增按 vendor/model_name 解析并支持前缀缓存失效;

- 新增 resolve_llm_config_by_model_name(full_name):拆分 vendor/model_name,
  叠加 model_configs JSONB 与 vendor_configs 凭证,产出 LiteLlm kwargs;
  60s TTL 缓存键 llm_name:<full_name>;miss 记录结构化告警。
- 新增 resolve_subagent_model_name(agent_name):读取 sub_agents.model
  字段,未启用或空值返回 None;60s TTL 缓存键 subagent:<name>。
- invalidate_cache 扩展 prefix 参数,SubAgent PATCH/DELETE 端点在提交后
  按 subagent:<name> 前缀失效;重命名时对旧名与新名双端清理。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(agents): 拆分 create_root_model/create_subagent_model 并落地动态 LiteLlm;

- 新增 agents/_dynamic_model.py:基座 _DynamicLiteLlm 以 object.__setattr__
  绕过 pydantic 校验;每轮 generate_content_async 前按策略解析覆盖配置,
  asyncio.Lock 串行 swap→call→restore,finally 还原原始 model 与 args。
- DynamicRootLiteLlm 从 ContextVar 读 Home 选择;DynamicSubagentLiteLlm
  按 agent_name 查 sub_agents.model,解析失败/空值直接走父类默认路径。
- agents/_model.py 拆分 create_root_model / create_subagent_model;
  create_model 保留为静态 LiteLlm 向后兼容。
- agents/agent.py 根 Agent 注入 before_model_callback,从 session.state
  读取 selected_llm_model 置入 ContextVar。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(faculties): 按 agent name 联动 sub_agents.model;

五大 Faculty 构造函数由 create_model() 迁移为
create_subagent_model(agent_name="<FacultyName>"),各自名字与
subagent_presets 同步写入 sub_agents.name 保持一致:
Perception/Action/Contemplation/Influence/InternalizationFaculty。

运行时每轮请求从 sub_agents.model 读取 vendor/model_name;命中覆盖
单轮 self.model 与 litellm_kwargs,空/未命中回退默认 LLM。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(home): Composer 加入 LLM 选择器并按 Thread 持久化;

- 新增 components/ui/LlmModelSelect.tsx:按 vendor 分组的 <optgroup>
  下拉,value 使用 vendor/model_name 规范化字符串,保留「未知」回显以
  兼容旧字段;Home 与 SubAgents 表单共用。
- Composer 左下加入 LLM 选择器 + Shift+Enter 提示组合;Props 扩展
  models / selectedLlmModel / onSelectedLlmModelChange。
- home-body 拉取 LLM 选项(失败写 llm_options_fetch_failed 日志),
  按 sessionId 在 ref 缓存维护 per-Thread 选择,优先 ref,其次从
  snapshot.selected_llm_model 初始化;doSend 前将选择注入
  agent.forwardedProps.selected_llm_model。
- /api/agui route 将 forwardedProps.selected_llm_model 转写为上游
  state_delta:字符串写入,显式 null 清除,缺失则不变更。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(interface-subagents): Model 字段改为 LLM 下拉;

SubAgentFormDialog Model 字段由自由文本 input 改为复用
components/ui/LlmModelSelect:挂载时 fetchModelConfigs 拉取启用 LLM
选项,value 使用 vendor/model_name 规范化字符串;旧值不在列表中保留
「未知」回显,用户保存时才以列表值覆盖,避免静默清洗。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(models): 卡片展开时同行卡片高度不再被拉伸;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(pipeline-runs): 修复 Pipeline Runs 延迟可见性问题,提前创建 Pipeline 记录;

将 ingest_url (as_document) 和 sync_document 端点中的耗时同步操作
(URL 提取、文档存储等)移至后台任务执行,确保 Pipeline Run 记录
在请求返回时即已创建,Dashboard 轮询可立即发现新任务。

同时修复前端 Bootstrap Polling 在已有 running 状态时跳过检测的问题,
确保并发场景下新 Run 也能被快速发现。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(adk-agent-loader): 修复 Home 页对话框发送消息 500 错误及模型选择重置;

根因为 cli.py 的 --reload_agents 参数值 src/negentropy 使 ADK AgentLoader
在 src/negentropy/ 下查找名为 negentropy 的子模块(期望 src/negentropy/negentropy/),
但 Agent 实际定义在 src/negentropy/ 本身(通过 __init__.py 的 __getattr__ 导出 root_agent)。
将 agents_dir 从 src/negentropy 改为 src,使 ADK 在 src/ 下正确发现 negentropy 包;
同步在 src/ 下新建 services.py 作为 ADK load_services_module 的 service bridge,
确保 apply_adk_patches() 仍被执行。前端模型选择重置为 500 错误的次生问题,后端修复后自动解决。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(AGENTS): 新增 Issue 摘要维护规范,要求在 docs/issue.md 中记录处理过的 Issue;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(knowledge-catalog): 修复 Catalog 目录页空状态与文档分配链路;

- CatalogTree 常驻「添加根节点」按钮,空态仅作条件提示
- CreateNodeDialog 的 slugify 对齐后端 ASCII-only 校验(去除 CJK 范围),
  附带 URL 友好说明,避免同步到 Wiki 时产生非法 slug
- 修复 assignDocumentToNode:前端改用批量端点 POST /documents
  并将代理路由从 [docId]/route.ts 迁回 documents/route.ts
- [docId]/route.ts 仅保留 DELETE,消除不存在的单文档端点

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(knowledge-wiki): 打通 Catalog→Wiki 全量同步管线;

- wiki_service.sync_entries_from_catalog 重写为递归全量同步:
  * CatalogDao.get_subtree 递归子树 + Materialized Path 层级 slug
  * markdown_extract_status 就绪校验、slug 冲突递增兜底(uq 约束保护)
  * 环检测修正(cur_id 使用当前节点而非循环变量)
  * errors 前缀枚举:skip / renamed / cycle_detected / empty_subtree
  * 末尾 remove_stale_entries 保证幂等清理
- wiki_service._slugify 严格 ASCII-only(对齐 wiki slug 校验正则)
- wiki_dao.get_nav_tree 按 entry_order(JSON path) 构建嵌套树,合成容器节点
- wiki_dao.remove_stale_entries 支持 document_id 差集清理
- lifecycle_schemas 新增 SyncFromCatalogRequest / SyncFromCatalogResponse
- api.py 暴露 POST /wiki/publications/{pub_id}/sync-from-catalog,
  沿用 AsyncSessionLocal 上下文,docstring 明确全量语义与 ISR 时延

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(knowledge-catalog): 增强 Catalog 节点与文档管理;

- CatalogTreeNode 行内新增 hover 可见的「+ 子节点」按钮,
  支持在任意节点下快速创建子节点,stopPropagation 避免误触选中
- NodeDetailPanel 底部挂载 DocumentAssignmentSection:
  * 列出节点已归属文档 + 单项移除
  * AddDocumentsDialog 复用 fetchDocuments + assignDocumentToNode,
    支持关键字搜索、批量勾选、已归属去重
- page.tsx 透传 corpusId;面板整体 overflow-y-auto 以容纳文档区

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(knowledge-wiki): 新增 Wiki 管理前端与一键同步发布工作流;

- Wiki API 客户端与 11 个 Next.js 代理路由(publications/entries/nav-tree/sync-from-catalog/publish/unpublish)
- /knowledge/wiki 双栏管理页:左列 CorpusSelector + 发布列表,右列详情面板
- CreateWikiPublicationDialog(ASCII slug 自动衍生)
- CatalogNodeSelectorDialog 复选树 + 全量覆盖告警
- 「从 Catalog 同步」与「同步并发布」一键流 + ISR 5 分钟提示
- WikiEntriesList 基于 nav-tree 嵌套缩进展示(兼容容器节点 entry_id === null)
- KnowledgeNav 追加 Wiki 导航项

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(wiki-ssg): SSG 层级导航与 catch-all 路由支持;

- 新增 `WikiNavTree` 递归组件,支持容器节点(entry_id === null)
  与叶节点的层级展示,并接入 activeSlug 高亮;Publication 入口
  页与文档页共用同一导航组件。
- 路由 `[pubSlug]/[entrySlug]` 迁移为 catch-all
  `[pubSlug]/[...entrySlug]`,修正 entry_slug 含 "/" 的层级路径
  此前 404 的问题;`generateStaticParams` 将 slug 按 "/" 分段。
- 文档页新增「pending」空态卡片,当源文档 Markdown 尚未提取完成
  时引导用户至 Knowledge › Documents 触发重提取;与「missing」
  态明确区分。
- 更新 `WikiNavTreeItem` 类型,`entry_id` / `document_id` 容
  器节点可为 null;Publication 入口页使用递归 `findIndexEntry`
  与 `countLeafEntries`。
- globals.css 追加 `.wiki-nav-group` / `.wiki-nav-sublist` /
  `.wiki-pending-card` 样式 token。

Plan: 打通 Catalog→Wiki 全链路(v3 Review 修订版) P4.1-4.3

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(issue): 记录本轮 Catalog→Wiki 全链路打通的问题摘要;

新增 `docs/issue.md`,按 AGENTS.md 规范追加 4 条 Issue 摘要:

- ISSUE-001:Catalog 页空状态隐藏「添加根节点」入口(UI 误用
  early return 吞掉主操作)。
- ISSUE-002:`sync_entries_from_catalog` 死代码 + 契约缺口
  (未递归 subtree、无 slug 冲突兜底、API 未暴露、nav-tree
  扁平)。
- ISSUE-003:SSG 路由不支持层级 slug(单段参数 vs. catch-all)。
- ISSUE-004:前端 slug 字符集与后端正则不一致(中文 slug 被
  后端正则拒收)。

每条摘要覆盖「表因 / 根因 / 处理方式 / 后续防范 / 同类问题影响」
五个维度,便于跨上下文复盘与防范。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(backend-url): 彻底清除 6600/6666 历史端口守护与残留;

- 精简 apps/negentropy-ui/lib/server/backend-url.ts:删除 LEGACY_LOCAL_PORTS、
  LOCAL_HOSTS、CURRENT_BACKEND_PORT、warnedUrls、isLegacyLocalhostUrl、
  applyLegacyPortMigration、pickFirstNonEmpty、__resetLegacyPortWarningsForTests,
  保留纯 SSOT 解析逻辑(152 行 → 78 行);
- 同步精简 tests/unit/lib/server/backend-url.test.ts:删除 legacy-port 4 个
  describe 块与相关 import,保留「默认值」「优先级链」共 8 条用例(180 行 → 95 行);
- .env.example 删除 2 行历史端口迁移注释;
- docs/development.md 删除「迁移守护」block quote;
- docs/sso.md 在 §8 环境变量表后追加 1 行运维提示,指引同步更新本地 .env 与
  Google Cloud Console OAuth 授权重定向 URI 白名单;
- CHANGELOG.md 在 [Unreleased] ### Removed 段登记守护退役条目;
- docs/issue.md 追加 ISSUE-005,沉淀「兼容层必须附带退役期」等防范条款。

背景:6600/6666 → 3292 迁移已完成数月,守护机制却仍让 6600 作为「合法白名单值」
持续在代码、文档、测试夹具中循环出现,与运维侧 .env / Google Cloud Console OAuth
白名单残留互相掩盖,导致用户误判「PR 引入端口回退」。按 AGENTS.md「最小干预 +
熵减 + 单一事实源」原则,彻底清除守护机制,让 3292 成为唯一、无历史包袱的权威端口。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(ui/auth-guard): 补齐未登录引导页 dark 变体,消除 Knowledge/Memory 暗色失真;

AuthGuard 的 loading 与 unauthenticated 两个分支此前硬编码 bg-zinc-50 /
text-zinc-* / bg-black 等浅色 Tailwind 类,未配套 dark: 变体,导致未登录
访问 /knowledge/*、/memory/* 时暗色模式下仍以亮色渲染;对照 Home 页
app/page.tsx L34-65 的同款引导 UI 逐字对齐,新增 6 处 dark: 类
(dark:bg-zinc-950 / dark:text-zinc-100 / dark:text-zinc-400 /
dark:bg-white dark:text-black dark:hover:bg-zinc-200),Home 页路径与
已登录路径零影响。同步在 CHANGELOG.md 追加 Fixed 摘要,在 docs/issue.md
登记 ISSUE-006(表因/根因/处理方式/后续防范/同类问题影响)。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(knowledge-bff): 修正 Catalog/Wiki 代理 /knowledge 前缀与跨域 POST/PATCH search 转发;

- 补齐 13 处 BFF 代理 path 的 /knowledge 前缀(Catalog 5 + Wiki 8),消除
  POST /api/knowledge/catalog 等「添加根节点」链路的 FastAPI 404;与
  Memory/Interface 域及 Knowledge 域既有 17 条合规路由 100% 同构。
- 将 createCatalogNode 的 corpus_id 从 body 迁移至 query,对齐后端
  create_catalog_node(corpus_id: UUID = Query(...)) 的 Pydantic/FastAPI 契约,
  避免前缀修复后的二阶 422。
- 在 Knowledge/Memory/Interface 三域 _proxy.ts 的 proxyPost / proxyPostFormData /
  proxyPatch 中补齐 `upstreamUrl.search = new URL(request.url).search`,
  与同域 GET/DELETE 结构对称;消除潜伏的「POST/PATCH 不转发 Query 参数」跨域缺陷。
- knowledge/_proxy.ts 顶部沉淀 SSOT 头注释,明确 path 必含 /knowledge 前缀并给出正反例。
- 新增 tests/integration/knowledge-catalog-route.test.ts 集成单测,
  断言 POST 精确落到 http://localhost:3292/knowledge/catalog/nodes?corpus_id=<uuid>
  并覆盖 404 错误体透传,防止同类路径/参数漂移再次发生。
- CHANGELOG.md 与 docs/issue.md(ISSUE-007)同步记录根因/处理/防范,
  后端路由与 schema 零改动。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(security): 升级 python-dotenv 至 1.2.2 闭合 CVE-2026-28684 告警;

- `apps/negentropy/uv.lock`:将间接依赖 `python-dotenv` 由 1.2.1 升至 1.2.2(GHSA-mf9w-mj56-hr94 / CVE-2026-28684,CVSS 6.6 Medium),修补 `set_key` / `unset_key` 在跨设备 rename 回退时沿符号链接写入导致的任意文件覆盖(CWE-59/61);
- `docs/issue.md`:追加 ISSUE-008 记录本次告警的表因 / 根因 / 处理方式 / 后续防范 / 同类问题影响,沉淀「Dependabot 间接依赖 patch 定点升级」的统一处理范式;
- 验证:`uv lock --dry-run` 仅更新目标包无级联漂移,`uv sync --locked` 通过,`importlib.metadata.version('python-dotenv')` 运行期确认 1.2.2,仓库全文 grep 无 `set_key` / `unset_key` 调用,代码层零回归面。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(negentropy-wiki): 对齐 build/start 至 3092 并消除静态资源穿透与构建期伪失败;

- 新增 scripts/start-production.mjs(复刻 apps/negentropy-ui SSOT),注入 PORT=3092 / HOSTNAME=localhost,并在临时目录 symlink 回填 .next/static 与 public,解决 standalone 产物不自动拷贝静态资产导致 /_next/static/*.css 被 [pubSlug]/[...entrySlug] 动态路由吞并、放大为 ECONNREFUSED 噪声的根因;
- package.json start 脚本改走 wrapper,消除 3000 端口回退;
- src/app/page.tsx 将构建期日志降级为 console.warn(与兄弟路由风格一致),保留 ISR 自愈兜底;
- docs/negentropy-wiki-ops.md §5.2 / §5.3 / §8.1 同步端口、Dockerfile 与故障排除语义;
- CHANGELOG.md Unreleased → Fixed 段增补条目(含根因链路、SSOT 复用与 Release CI 爆炸半径约束)。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(catalog): 修复目录节点创建日志保留字段冲突;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)

Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(negentropy-wiki): 对齐文档表格格式与修正 API 基础地址;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* chore(negentropy-wiki): 新增 pnpm-workspace.yaml 工作区配置;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(knowledge): 修复目录文档列表与 wiki 文件名字段漂移;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)

Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* chore(negentropy-wiki): 新增 wiki 应用 favicon 与 logo 资源文件;

🤖 Generated with [Claude Code](https://github.com/claude)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(negentropy-wiki): 迁移 logo.png 至 public/ 规范静态资源目录;

按 Next.js 官方静态资源约定与同 monorepo 既有模式(negentropy-ui),
将 logo.png 从 src/app/ 迁出至 public/,便于通过 /logo.png 直接访问,
并与后续在 hero / sidebar 中引用的 URL 保持一致。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(negentropy-wiki): 接入 favicon 与首页/侧边栏 logo 品牌展示;

- layout.tsx: 声明 metadata.icons.apple 指向 /logo.png,favicon 由 App
  Router 约定自动注入 src/app/favicon.ico,避免 <link rel=icon> 重复。
- page.tsx: 首页 hero 区域在标题上方增加 96×96 logo。
- [pubSlug]/page.tsx 与 [pubSlug]/[...entrySlug]/page.tsx: 侧边栏
  顶部增加 28×28 小 logo,配合品牌容器(返回链接 / 标题省略号)。
- globals.css: 追加 .wiki-home-logo / .wiki-sidebar-brand /
  .wiki-sidebar-logo / .wiki-sidebar-title 样式,保障响应式与溢出处理。

next.config.ts 已设 images.unoptimized,故使用原生 <img> 以避免
next/image 的无效依赖,且配套 eslint-disable-next-line 附带原因注释。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(catalog): Phase 1 纯加法骨架——Catalog 全局化解耦 Corpus;

按 MediaWiki N:M + GitBook 订阅式发布范式,将 Catalog 从 Corpus 解耦为全局顶层实体。
本提交仅施加纯加法式变更(新增表、新增可空列),不触碰既有数据与约束,为后续
Phase 2 (backfill) 与 Phase 3 (enforce + drop) 提供基础骨架。

Alembic Revision 0003:
- CREATE TABLE doc_catalogs(app_name 不可变、乐观锁 version、软归档 is_archived)
- CREATE TABLE doc_catalog_entries(N:M;parent_entry_id 自引用树;document_id 软引用
  ON DELETE SET NULL;source_corpus_id 冗余权限字段;slug_override 支持一文多路径)
- CREATE TABLE wiki_publication_snapshots(snapshot 模式冻结 JSONB)
- CREATE TABLE wiki_slug_redirects(历史 slug→当前 slug 301 映射)
- ALTER TABLE wiki_publications ADD COLUMN(全部 nullable):
  catalog_id / app_name / publish_mode (LIVE|SNAPSHOT) / visibility / snapshot_version
- 新增 5 个 enum 类型:catalogvisibility / catalogentrynodetype / catalogentrystatus
  / wikipublishmode / wikipublicationvisibility
- 完备 downgrade:严格按 upgrade 逆序回滚,纯加法式 → 回滚无数据丢失风险

ORM 模型(apps/negentropy/src/negentropy/models/perception.py):
- 新增 DocCatalog / DocCatalogEntry / WikiPublicationSnapshot / WikiSlugRedirect
- 扩展 WikiPublication:新增 catalog/publish_mode/visibility/snapshot_version 等可空字段
  与 catalog/snapshots/slug_redirects 关系,旧 corpus FK 保持不变(过渡期双写)
- 旧 DocCatalogNode / DocCatalogMembership 保留不动,Phase 3 在 backfill 完成后
  统一 drop

本地验证:
- NE_DB_URL=postgresql+asyncpg://... uv run alembic upgrade head → ✓
- uv run alembic downgrade 0002 → ✓(新表/新列/新 enum 全部清理干净)
- uv run alembic upgrade head 再执行 → ✓(幂等)
- uv run pytest tests/integration_tests/db/test_migrations.py -v → 4 passed
  (single head / stairway / perceives seed / seed 幂等 全绿)

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(catalog): Phase 2 回填迁移——legacy 目录树平移至全局 Catalog 骨架;

将 Phase 1 创建的新骨架(doc_catalogs / doc_catalog_entries)以 1:1 映射
回填既有 doc_catalog_nodes + doc_catalog_memberships 与 wiki_publications 数据。

回填策略(纯 DML,无结构变更 except 临时映射列):
  - 临时列 doc_catalogs.legacy_corpus_id(Phase 3 上线时清理)承载映射键;
  - 每个被引用 corpus → 1 个 doc_catalogs(slug='corpus-{id[:8]}');
  - doc_catalog_nodes.id 原样作为 doc_catalog_entries.id,
    parent_entry_id = 原 parent_id,自然延续树形结构;
  - doc_catalog_memberships → DOCUMENT_REF 叶子 entry,
    name = '{original_filename} #{doc_id_prefix}' 防重;
  - wiki_publications 的 catalog_id / app_name / publish_mode='LIVE' /
    visibility='INTERNAL' 同步回填。

幂等与可逆:
  - INSERT ... WHERE NOT EXISTS 保证重复执行安全;
  - downgrade 精确反向:清空回填数据 + NULL 化 publication 字段 +
    DROP 临时映射列,不破坏 legacy 表。

本地验证:stairway (upgrade head → downgrade base → upgrade head),
负载场景 (seed 1 corpus + 2 nodes + 1 membership + 1 publication) 回填正确,
pytest tests/integration_tests/db/test_migrations.py (4 passed, 12.27s)。

🤖 Generated with [Claude Code](https://github.com/claude)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* Commit changes from coding agent for workspace 67c7ae95-82f3-410e-90a3-36ac274aff28

* refactor(catalog): Phase 3 ORM 切换——WikiPublication 移除 corpus_id 并绑定 catalog_id;

- perception.py: WikiPublication 删除 corpus_id 列与 corpus 关系;catalog_id/app_name/publish_mode/visibility 改为 NOT NULL;__table_args__ 约束从 uq(corpus_id,slug) 切换至 uq(catalog_id,slug),移除 postgresql_where 部分索引
- wiki_dao.py: create_publication/get_publication_by_slug/list_publications 形参与 filter 全部从 corpus_id → catalog_id
- wiki_service.py: create_publication/list_publications 传参同步切换
- models/__init__.py: 移除 DocCatalogNode/DocCatalogMembership 导出(Phase 3 已 DROP)

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(catalog): Phase 3 ORM 切换——全局化 DAO/Service/Schema/API 适配 DocCatalogEntry;

- catalog_dao.py 全面重写:基于 DocCatalog + DocCatalogEntry 实现顶层 Catalog CRUD 及节点操作,Recursive CTE 改为 catalog_id 为根,assign_document 通过 DOCUMENT_REF 子条目替代旧 DocCatalogMembership
- catalog_service.py 重写:corpus_id → catalog_id 维度切换;新增 create/list/update/archive/delete_catalog 委托;assign_document 加入跨 app_name 权限断言(PermissionError)
- lifecycle_schemas.py:WikiPublicationCreateRequest/Response、CatalogNodeResponse 中 corpus_id → catalog_id
- api.py:catalog tree/nodes 端点 corpus_id → catalog_id;新增 _entry_orm_to_resp 辅助函数处理 DocCatalogEntry → Schema 字段映射;wiki publication create/list 参数同步更新
- 单元测试同步适配新接口:test_catalog_dao_unit / test_wiki_service_unit corpus_id → catalog_id,断言字段适配 DocCatalogEntry(slug_override/position/node_type 大写)

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(ui): Phase 3 Catalog 全局化——前端 / BFF / Wiki SSG 适配;

- knowledge-api.ts:CatalogNode.corpus_id → catalog_id,WikiPublication.corpus_id → catalog_id,新增 DocCatalog 类型、fetchCatalogs()、fetchCatalogDocuments()、WikiPublishMode;
- 新建 CatalogSelector 组件替代 CorpusSelector 作为 Catalog 顶层选择器;
- catalog 页面组件(useCatalogTree / CreateNodeDialog / NodeDetailPanel / DocumentAssignmentSection / AddDocumentsDialog / page)全面切换 catalogId 参数;
- wiki 页面组件(WikiPage / CreateWikiPublicationDialog / WikiPublicationDetail)同步切换 catalogId;
- BFF 代理:/catalog/tree/[corpusId] 废弃返回 410 Gone;新增 /catalogs/** 代理路由 8 条;
- negentropy-wiki wiki-api.ts:corpus_id → catalog_id,WikiEntry 补充 status 字段,WikiPublication 新增 publish_mode / app_name;
- wiki SSG 条目详情页:新增 orphaned status 渲染占位,替换 findEntryId 为并发拉取 entries 以支持状态检测。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* test(catalog): Phase 3 集成测试更新 + 跨 corpus / 发布模式覆盖;

- 重写 test_catalog_dao_integration.py:新增 sample_catalog fixture,所有 create_node/get_tree 改用 catalog_id;对齐新 ORM 字段(slug_override、position、parent_entry_id)
- 新增 test_catalog_cross_corpus.py:覆盖跨 app_name 权限拒绝、catalog 隔离、orphaned entry 语义
- 新增 test_wiki_publish_modes.py:覆盖 WikiPublishingService 完整生命周期(draft→published→archived)、版本递增、catalog 过滤、slug/theme 校验
- 新增 test_catalog_tree_perf.py:固化 get_tree/get_subtree/list_catalogs P99 基线(< 50/20/10ms)

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(catalog): Phase 3 架构文档 / ISSUE / CHANGELOG 同步;

- knowledges.md §13 新增「Catalog / Wiki Publication 三层正交架构」:Mermaid ER 图、权限三级取交集、失效语义、发布模式对比、三阶段迁移概览、Catalog 专项测试索引
- knowledges.md §13(原 §13)重编号为 §14 测试覆盖
- issue.md 追加 ISSUE-011:Catalog 全局化三阶段重构根因分析、处理步骤、防范要点与同类问题影响
- CHANGELOG.md [Unreleased] 新增 Breaking Change 条目:corpus_id 替换说明 + 四步迁移指南

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(catalog): 补齐后端 /catalogs RESTful 路由并清理旧 /catalog/nodes 契约;

Phase 3 Catalog 全局化重构中,前端/BFF 已迁移至 /catalogs/{catalogId}/entries
新路径,但后端 api.py 仅保留旧 /catalog/nodes 路由,导致创建目录节点时返回
"method not allowed"。此次修复按"新增 + 迁移 + 清理"一体化原则收敛契约:

- 后端新增 3 个 Catalog CRUD Schema(CatalogCreate/Update/Response)与
  _catalog_orm_to_resp helper,并注册 15 条 /catalogs 全量 RESTful 路由
  (含直接修复 bug 的 POST /catalogs/{catalog_id}/entries),复用既有
  CatalogService / CatalogDao,无数据层改动。
- 后端删除 291 行旧 /catalog/nodes 路由(get_catalog_tree /
  create_catalog_node / update_catalog_node / delete_catalog_node /
  assign_documents_to_node / unassign_document_from_node /
  get_node_documents 等 11 个函数),消除双契约风险。
- 前端迁移 7 个 knowledge-api.ts 函数签名至新路径(新增必填
  catalog_id 参数),同步适配 NodeDetailPanel / AddDocumentsDialog /
  DocumentAssignmentSection 三个调用方组件,补齐 useCallback 依赖。
- BFF 删除废弃 app/api/knowledge/catalog/ 目录(5 个旧代理路由文件)。
- 测试:集成测试 knowledge-catalog-route.test.ts 重写匹配新路径;
  单测 test_get_node_documents_uses_original_filename 改名为
  test_get_entry_documents_uses_original_filename 并适配新函数签名。
- CHANGELOG.md 同步记录本次修复。

验证:前端 pnpm typecheck + typecheck:test + lint 通过;前端集成测试
knowledge-catalog-route 2/2 通过;后端 15 条新路由经 router.routes
自省确认注册;后端 tests/unit_tests/knowledge/ 全量 414 测试通过。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(db-migration): 修复 0005 downgrade 路径 LOWER(enum) 报错与 DOCUMENT_REF 回填 FK 违反;

- 将 LOWER(e.node_type) 显式改写为 LOWER(e.node_type::text),与 0004 upgrade 的 UPPER(...)::enum 形成对称 cast 链路,规避 PG text-only 标量函数无枚举重载的限制;
- step 3c 补齐 JOIN doc_catalog_nodes 过滤条件,与 0004 upgrade 的 membership→node→catalog 三连 JOIN 对称,防止 DOCUMENT_REF 嵌套语义穿透 FK 约束;
- CHANGELOG 与 docs/issue.md 补录 ISSUE-012 根因分析及同类问题防范。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(catalog): 修复添加文档对话框候选为空且文件名缺失的双缺陷;

根因一: GET /knowledge/catalogs/{catalog_id}/documents 端点语义与前端契约反向——后端查询 DocCatalogEntry 返回"已归属文档", 而 AddDocumentsDialog 期望的是"可分配候选文档";
根因二: 响应内联 dict 以 "filename" 为键, 漏 app_name/file_hash/gcs_uri 等字段, 前端按 original_filename 读取得到 undefined, 渲染空文件名 (ISSUE-010 同型回归);
修复: get_catalog_documents 改为加载 DocCatalog -> 取 app_name -> 复用 DocumentStorageService.list_documents(app_name=…) + _build_document_response() 序列化 SSOT; get_entry_documents 同步改用 _build_document_response() 修补同型字段漂移 (语义不变); 未知 catalog_id 从静默空结果升为 HTTP 404 + CATALOG_NOT_FOUND;
新增 5 个集成用例 (跨 app 隔离、软删过滤、未知 catalog 404、字段完整性、归档 catalog) 锁定契约; CHANGELOG 与 ISSUE-010 同步记录回归事件与"列表端点必须复用 _build_document_response"强制规则;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(knowledge): 预置 negentropy-perceives 的 4 个默认 MCP 提取工具;

新增 Alembic 迁移 0006_seed_negentropy_perceives_tools,幂等 upsert 4 行
mcp_tools(parse_webpage_to_markdown / parse_webpages_to_markdown /
parse_pdf_to_markdown / parse_pdfs_to_markdown),与 0002 的预置 Server
正交对称,填补新建 Corpus 时 _resolve_default_extractor_routes() 的 DB 前提。

修复「新建 Corpus 的 Document Extraction Settings 面板主/备下拉全部
显示『未配置』」的 UX 缺陷;_resolve_default_extractor_routes 与前端
零改动,与 live discovery 的 UPSERT 分支天然兼容。

同步更新 CHANGELOG.md (Unreleased → Fixed) 与 docs/issue.md (ISSUE-013
沉淀「预置数据应与依赖它的应用逻辑一起交付」经验)。

Stairway 迁移回环与 corpus 单测全部 PASS;手工 DB 查询确认 4 行预置落地。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(AGENTS): 补充数据库操作安全规范,强调迁移回滚需谨慎;

🤖 Generated with [Claude Code](https://github.com/claude)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(KnowledgeBase): 修复 Corpus Settings 页导航失效(Documents/Back 按钮无响应、跨页返回未重置视图);

根因:syncQueryState 使用 Next.js router.replace() 在同路径替换时触发路由缓存去重,
searchParams 对象引用不变导致 useEffect 不重新触发状态同步。

修复:
- 将 router.replace() 替换为 window.history.replaceState() + 直接同步 React 状态
- useEffect 依赖从 [searchParams] 改为 [searchParams.toString()] 按内容比较
- 移除不再使用的 useRouter 引用

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(KnowledgeCatalog): 修复未选目录时「创建根节点」触发 POST /api/knowledge/catalogs/entries 405 Method Not Allowed(Layer 1 UX 入口门禁 + Layer 2 API 客户端空值守卫 + Layer 3 回归测试);

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* docs(catalog-singleton): 沉淀单实例 Catalog 收敛(Phase 4)架构与运维文档;

- knowledges.md §13 增加下游收敛指针 → 新增 §15「单实例 Catalog 收敛(Phase 4)」作为 ADR 等价记录:
  设计动机、partial unique index DDL、与 Phase 3 N:M 关系(叠加非回退)、根节点合并为子树 Mermaid 图、
  Confluence/GitBook/Notion 业界范式映射、风险缓解、IEEE 引用 9-14(GoF Composite、Evans DDD、Kleppmann DDIA、Sadalage 进化式数据库);
- negentropy-wiki-ops.md 新增 §12「单实例 Catalog 与 Wiki 发布版本管理运维」:
  日常巡检 SQL 4 项不变量、Phase B Merge Runbook(前置检查 / 强制 pg_dump / 守恒断言 / 回退)、
  灰度监控矩阵、WikiPublication 多版本回退 SQL、故障应对表;
- issue.md 新增 ISSUE-014「Catalog/Wiki 选择器冗余 → 单实例收敛」:
  表因 / 根因(产品形态与 schema 表达力不对称)/ 五步处理方式(架构沉淀 → Migration 0007 → 0008 → 后端 → 前端)/
  五条防范(含「冷启动空载是聚合根缺失早期信号」)/ 四类同类问题影响。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(catalog-singleton): 引入 Phase A 聚合根不变量约束(partial unique + merged_into_id 列);

按 Expand → Backfill → Contract 三段式不破坏迁移策略落地扩张阶段(纯加法、零数据破坏):

- 新增列 doc_catalogs.merged_into_id UUID NULL REFERENCES doc_catalogs(id) ON DELETE SET NULL
  Tombstone 溯源指针:合并完成后源 catalog 标记 is_archived=true 并指向 survivor,保留双向溯源关系
- 新增 partial unique index uq_doc_catalogs_app_singleton ON doc_catalogs(app_name) WHERE is_archived = false
  聚合根不变量:每个 app_name 仅允许 1 个活跃 Catalog(DDD Aggregate Root)
- 新增 partial unique index uq_wiki_pub_catalog_active ON wiki_publications(catalog_id) WHERE publish_mode = 'LIVE'
  每个 Catalog 仅允许 1 个 LIVE 模式 WikiPublication;SNAPSHOT 模式作为版本回退池累积,不受约束
- 新增 partial index ix_doc_catalogs_merged_into_id WHERE merged_into_id IS NOT NULL
  仅覆盖 tombstoned 行,绝大多数行 NULL 不计入索引

设计要点:
- 约束字段选择 publish_mode(LIVE/SNAPSHOT 模式维度)而非 status(draft/published/archived 生命周期维度)—— 二者正交
- downgrade 仅 DROP 新增索引/列,无副作用,与 test_migrations_stairway base ↔ head 往返兼容
- 测试环境 fixture 库为空,索引创建无冲突;生产环境若已累积多 catalog/多 LIVE publication,须按 docs/negentropy-wiki-ops.md §12 runbook 先手工合并再升级

验证:
- alembic heads → 0007 (head)
- 三循环 upgrade head → downgrade -1 → upgrade head 干净往返
- 约束断言通过:2 个 active 同 app_name 被 uq_doc_catalogs_app_singleton 拒绝;1 active + 1 archived 共存放行;1 LIVE + 多 SNAPSHOT 同 catalog 共存放行;2 LIVE 同 catalog 被 uq_wiki_pub_catalog_active 拒绝

设计溯源(IEEE 引用见 docs/knowledges.md §15):
- [2] E. Evans, *Domain-Driven Design*, Addison-Wesley, 2003. — Aggregate Root
- [4] M. Kleppmann, *Designing Data-Intensive Applications*, O'Reilly, 2017. — Tombstone
- [5] P. J. Sadalage and M. Fowler, *NoSQL Distilled*, ch. "Schema Migrations", 2016. — Expand-Contract

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(wiki-publication): 清理 Wiki 发布的 dormant 字段(navigation_config + custom_css + custom_js);

三字段在前后端全链路 dormant(零业务消费),按 YAGNI + Entropy Reduction 原则移除。
Migration 0008 为纯 DDL Contract 阶段(DROP COLUMN),downgrade 重建为 nullable 列。

变更范围:
- 新增 Migration 0008(drop 3 列 + downgrade 重建)
- ORM: WikiPublication 移除 3 mapped_column 声明
- DAO: create/update 移除三字段透传
- Schema: UpdateRequest/Response 移除三字段
- Types: dataclass 移除 navigation_config
- TS: interface 移除 navigation_config + UpdateParams 移除三字段

验证:Alembic 三循环通过、TypeScript 类型检查通过、ruff lint/format 通过

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Aurelius Huang <threefish.ai@gmail.com>

* fix(PipelineTracker): 修复 Pipeline Run 卡在 RUNNING 状态不收敛(ensure_finalized 安全网 + finally 终态保障 + stale 回收 + 统一 return [] 语义)

根因:所有 execute_*_pipeline 后台任务方法仅 try/except Exception,缺少 finally 安全网——当异常处理链自身失败(DB 写入异常)或后台任务被取消(CancelledError 是 BaseException,不被 except Exception 捕获)时,PipelineTracker 状态永远停留在 running。

修复:
1. PipelineTracker 新增幂等安全网方法 ensure_finalized()(已终态 noop,未终态强制 failed)
2. 全部 8 个 execute_*_pipeline 方法追加 finally 块调用安全网
3. 4 个方法的 raise 统一改为 return [](与其它 4 个一致,避免 uvicorn 异常堆栈)
4. DAO 层新增 finalize_stale_pipeline_runs() 批量回收超时 running 记录

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(catalog-ui): 完善 Catalog 目录树维护组件(工具栏 + 上下文菜单 + 内联重命名 + 拖拽排序);

新增 CatalogTreeToolbar(搜索过滤、全部折叠、添加根节点按钮)、CatalogContextMenu(右键上下文菜单)、EmptyCatalogState(空目录引导状态);
扩展 useCatalogTree hook 支持 expandAll/collapseAll/renameNode/moveNode;
CatalogTreeNode 集成内联重命名、HTML5 拖拽排序(三区域放置 + 循环检测)、MoreHorizontal 操作按钮;
更新 docs/knowledges.md §16 记录 Catalog UI 维护组件设计。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* feat(catalog-ui): 移除 CatalogSelector,自动绑定单实例目录(useSingletonCatalog + createCatalog API + 直接渲染工具栏/树)

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(dashboard): 修复从 Knowledge/Document 切换到 Dashboard 时 Pipeline Runs 不显示的问题;

将初始加载的 Promise.all 拆为两个独立异步 IIFE,使 fetchPipelines 完成后立即
应用数据,不受 fetchDashboard 延迟阻塞;引入 useRef 稳定 bootstrap 轮询
effect 的 baseline snapshot,移除依赖数组中不稳定的 pipelinesPayload?.runs,
消除反复 cleanup/restart 导致的变更检测失效循环。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(wiki-publication): 修复 Wiki 发布创建 500 错误(app_name NOT NULL 违约)并移除目录选择器;

后端:create_wiki_publication handler 通过 db.get(DocCatalog) 查询目录提取 app_name,
透传至 service → DAO 层显式写入 WikiPublication ORM 对象,解决 Phase 3 迁移新增的
app_name NOT NULL 列未在创建链赋值导致的 500 Internal Server Error。

前端:Wiki 页移除 CatalogSelector,改用 useSingletonCatalog() 自动绑定唯一根目录,
与 Catalog 页 Phase 4 单实例收敛对齐。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(wiki-publication): 修复 Wiki 发布列表 GET 500(session 关闭后访问懒加载关系);

list_wiki_publications / get_wiki_publication 两处 handler 在 async with AsyncSessionLocal()
退出后访问 pub.entries 懒加载关系,session 已关闭触发 DetachedInstanceError。
将 entries 计数逻辑移入 session 上下文内。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(wiki-publication): 修复 Wiki 发布列表 GET 500 三阶问题(async 懒加载需 eager-load);

二阶修复将 len(pub.entries) 移入 async with 上下文内后 DetachedInstanceError
消解,但异步 SQLAlchemy 2.0 不支持隐式懒加载触发 IO——Base 未继承 AsyncAttrs、
entries 关系无 lazy="selectin"、DAO 查询无 selectinload(...),致首次属性访问以
sqlalchemy.exc.MissingGreenlet 失败,外观仍为 500。

修复(最小干预 + 正交分解):在 wiki_dao.py 的 list_publications / get_publication
两处 select(WikiPublication) 上挂 selectinload(WikiPublication.entries),让
entries 随主查询一次性物化(IN 批量查,1+1 两条 SQL,limit≤200 性能足够),
response 序列化彻底脱离 session/lazy-load 状态依赖。api.py handler、Pydantic
schema、ORM 模型零改动;update_publication / publish / unpublish / archive /
delete_publication 内部复用 get_publication,自动受益且不触及 entries,零回归。

新增 2 条集成回归用例 test_list_publications_entries_accessible_after_query /
test_get_publication_entries_accessible_after_query,断言 pub.entries 在 await
返回后可直接读取且计数正确,锁定契约。

CHANGELOG 与 docs/issue.md ISSUE-016 同步追加三阶问题复盘——「session 存活」
不等于「关系可访问」,handler/serializer 中需要的所有关系必须在 DAO 查询层以
eager loading option 显式声明。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(knowledge-dao): 同型问题扫荡——审计 async DAO 懒加载契约并补强 wiki_dao 一致性缺口;

- WikiDao.get_publication_by_slug 补 selectinload(WikiPublication.entries),与 get_publication / list_publications 同构(PR #407 修复后唯一一致性缺口;当前无调用方,预防未来调用复现 ISSUE-010 三阶)
- WikiDao / CatalogDao / SourceDao 类 docstring 明示「async 懒加载契约」,沉淀「为何安全 / 何时需补 eager-load」的隐式知识
- docs/issue.md ISSUE-010 三阶段落末尾追加「同型扫荡审计结论」(4 类 DAO + 3 类结构性回避模式 + Base.AsyncAttrs 未来债务清单)
- CHANGELOG.md [Unreleased] 新增 ### Refactor 段记录本次审计与最小干预补强
- 零功能行为变更,零回归风险;ruff check / format --check 全部通过

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(wiki-nav-tree): 修复 Wiki 发布卡点击触发整页 ErrorBoundary 崩溃(前后端契约漂移四阶);

negentropy-ui 与后端 + negentropy-wiki SSG 在 nav-tree 响应契约上存在三处事实漂移:
字段名错位(slug/title vs entry_slug/entry_title 且缺 is_index_page)、
children 必选语义错位、{items} 信封外壳错位。
最终在 WikiEntriesList.flattenNavTree 的 for...of 上以
TypeError: object is not iterable 引爆。

前端单边对齐后端 + SSG(SSOT 不可动):
- knowledge-api.ts 的 WikiNavTreeItem / WikiNavTreeResponse 类型重命名 + 信封化
- WikiPublicationDetail.tsx setNavTree(resp.nav_tree?.items ?? []) 兜底
- WikiEntriesList.tsx 字段访问对齐 + 移除 pathPrefix 二次拼接
  (后端 entry_slug 已存全路径,与 SSG WikiNavTree 同构)
- 新增 wiki-nav-tree.test.tsx(5 例)锁定契约
- CHANGELOG.md / docs/issue.md ISSUE-017 沉淀同 SSOT 多消费者教训

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* fix(wiki-bff): BFF proxyPost 容忍空 body,解开 publish/unpublish/job-action 阻塞;

knowledge / memory / interface 三个 _proxy.ts::proxyPost 均强制 await request.json(),
对空 body 立即抛 SyntaxError 返回 400,导致所有动作型 POST(Wiki publish/unpublish、
Memory automation jobs enable/disable/run/reconcile、MCP tools load 等)从 BFF 层就被
拦截而无法到达后端。改为先 request.text() 探测空白短路、非空再 JSON 校验透传,并仅在
有 body 时附 application/json content-type;非法 JSON 仍 400 短路(行为兼容)。新增
proxy-empty-body.test.ts 三例锁定(空透传 / 合法透传 / 非法拒绝)。详见 ISSUE-018。

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>

* refactor(wiki): 后端正交分解 + entry_path 命名规范化 + Snapshot/ISR webhook 实装;

按 AGENTS.md「正交分解 + 命名语义化 + Single Source of Truth」原则,对 Wiki 模块
后端做体系化梳理与语义增强,主要变更:

- 抽 negentropy.knowledge.slug 为前后端 slug 工具 SSOT,收敛历史三处重复实现
  (wiki_service._slugify / catalog_service._slugify / catalog_dao._compute_slug)。
- 抽 negentropy.knowledge.wiki_tree.build_nav_tree 为纯函数,将原 wiki_dao.get_nav_tree
  ~80 行嵌套树构建逻辑剥离至专用模块;DAO 仅返回平铺 entries,便于独立单测覆盖。
- 拆 WikiPublishingService.sync_entries_from_catalog 为 _collect_subtree_documents +
  _build_path_slugs(纯函…
ThreeFish-AI added a commit that referenced this pull request May 17, 2026
…为权威实现收敛 123 处冲突;

- 基准 merge-base: bc6f173(chore(merge): 回流 master c0bb18f)
- master-only commits(均为 feature/1.x.x 演进的子集回流):
  * 94a709e release(feature/1.x.x): #462 Memory P1-7 + KG P1-5 + Skills P1-2;
  * a34bf19 Merge branch 'feature/1.x.x';
  * 8de1089 chore(release): 同步 PR #417/#418 至 master;
- 策略: -X ours 解析 123 处冲突 + checkout HEAD 复原 9 处 auto-merge(消除 instrumentation.py 等重复定义);
- 残留治理:
  * git rm: docs/issue.md(已迁至 docs/agents/issue.md)+ 2 个测试单文件(已被 #551 正交分解)+ test_consolidation_pipeline.py(已被 #551 拆分至 consolidation/);
  * git rm: 5 个调试截图(master 端 #462 squash 误回 + #4e10a54e 已声明移除);
- 合并后工作树与 feature/1.x.x HEAD 完全一致(git diff HEAD → 空),merge commit 仅记录拓扑;

🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant