docs(workflow): restore key agent rules and add concrete spec examples

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-10-15 16:25:47 +08:00
parent 51c94783bb
commit 4281ccbe13
14 changed files with 290 additions and 9 deletions

View file

@ -15,3 +15,22 @@
来源:[异常实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/exception/GlobalExceptionHandler.java)、
[响应与脱敏断言](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/exception/GlobalExceptionHandlerTest.java)、
[领域异常](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/shared/exception/LocalizedDomainException.java)。
## 场景:新增失败分类或修改统一异常处理
先定位失败属于领域、MVC 参数绑定、认证过滤器还是对象存储边界,再选现有异常与 handler。
例如对象存储读取失败应检查 `handleStorageAccess`;不能在下载服务捕获后返回空 ZIP 并声称成功。
| 输入/场景 | 当前可观察契约 | 回归锚点(GlobalExceptionHandlerTest) |
|---|---|---|
| `ObjectOptimisticLockingFailureException` | HTTP 409、响应 `code=409`、本地化冲突提示 | `persistenceConflictsReturn409WithoutLeakingDatabaseDetails` |
| `StorageAccessException` | HTTP 503;日志带 requestId 和认证状态,不输出稳定 userId | `handleStorageAccess_shouldLogAuthenticationWithoutStableUserId` |
| MVC 缺参、不可读请求体、类型不匹配 | HTTP 400、ApiResponse `code=400`;不记作未处理内部故障 | `handleMvcBadRequest_shouldReturn400WithoutUnhandledErrorLog` |
| 不支持的 HTTP 方法 | HTTP 405、ApiResponse `code=405`,保留 Allow 响应头 | `handleMethodNotAllowed_shouldReturn405AndAllowHeader` |
反例:用 `exception.getMessage().contains(...)` 为新业务规则分配状态码;只断言状态而遗漏协议头;
把原始存储异常文本直接发给客户端。正确做法是沿用有类型的异常、现有消息键与响应工厂,
在对应测试中同时断言状态、响应字段及敏感信息边界。
验收时执行 [后端测试入口](../testing/backend-testing.md) 对应测试;修改过滤器链或真实 HTTP 协议时追加 Controller/安全测试。
上表是当前单测覆盖范围,不证明事务回滚、真实存储可用性或所有入口均经过该 handler。

View file

@ -1,6 +1,6 @@
# 后端规范入口
适用 Java/Maven 后端。先读本页,再按改动选择专题;工程规范正文在本目录,不依赖工程 Skill 自动触发。
适用 Java/Maven 后端及 Python Scanner 本体。先读本页,再按改动选择专题;工程规范正文在本目录,不依赖工程 Skill 自动触发。
具体业务需求、权限和状态机归正式 docs/OpenSpec;规范整理不授权改变业务行为。
## 日常约定
@ -20,11 +20,21 @@
| HTTP、鉴权上下文、API消费方、生成类型 | [接口契约](./api-contracts.md) |
| 状态变更、重复请求、竞争或外部副作用 | [事务与并发](./transactions-and-concurrency.md) |
| 异常与响应错误 | [错误处理](./error-handling.md) |
| 日志、审计、追踪 | [日志](./logging-guidelines.md) |
| 扫描、队列、超时或重试 | [扫描集成](./scanning-integration.md) |
| 发布、下载、删除、授权行为变化,或日志、审计、追踪 | [日志](./logging-guidelines.md) |
| 扫描、队列、超时、重试或 Python Scanner 本体 | [扫描集成](./scanning-integration.md) |
| 实现与审查收尾 | [后端质量](./quality-guidelines.md)、[测试入口](../testing/index.md) |
生命周期修改读 [正式生命周期文档](../../../docs/14-skill-lifecycle.md) 及对应源码、测试;
API、坐标、权限修改读 [API设计](../../../docs/06-api-design.md)、[领域模型](../../../docs/02-domain-model.md)、[认证授权](../../../docs/03-authentication-design.md)。
发现文档与实现漂移时列出差异,不能按旧表机械改写代码;已知漂移见接口专题。
版本和模块清单以 [父 POM](../../../server/pom.xml) 为准,不将某次版本快照写成升级授权。
## 业务与基础设施路由
| 改动场景 | 修改前读取与核对 |
|---|---|
| 搜索条件、索引更新、搜索引擎替换 | [搜索设计](../../../docs/04-search-architecture.md)、[SearchQueryService](../../../server/skillhub-search/src/main/java/com/iflytek/skillhub/search/SearchQueryService.java)、[SearchIndexService](../../../server/skillhub-search/src/main/java/com/iflytek/skillhub/search/SearchIndexService.java);分别定位查询与索引端口,不将某个引擎的 SDK 泄漏到业务调用方 |
| 对象存储提供方、上传下载行为 | [架构设计](../../../docs/01-system-architecture.md)、[ObjectStorageService](../../../server/skillhub-storage/src/main/java/com/iflytek/skillhub/storage/ObjectStorageService.java)、[错误处理](./error-handling.md);先核对端口方法、现有实现和调用方,再判断是配置变化还是接口变化 |
| 发布、审核、下载等跨服务流程 | [业务流程](../../../docs/05-business-flows.md)、[事务与并发](./transactions-and-concurrency.md)、[日志与审计](./logging-guidelines.md);列出实际调用链的数据库写入、外部副作用与失败返回 |
上述是读取触发条件,不表示现有提供方已完成互换验证。涉及替换时按受影响行为验证,不凭“兼容协议”跳过验证。

View file

@ -15,3 +15,21 @@
[请求ID测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/filter/RequestIdFilterTest.java)、
[请求日志实现](../../../server/skillhub-app/src/main/java/com/iflytek/skillhub/filter/RequestLoggingFilter.java)、
[请求日志测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/filter/RequestLoggingFilterTest.java)。
## 场景:关键业务操作的审计随重构丢失
修改发布、下载、删除、授权及审核流程时,即使没有改日志代码,也必须检查审计链路。
先在旧调用链定位事件生产位置,记录 action、目标类型/ID、可信操作者、请求上下文以及记录发生在成功还是失败分支;
新调用链必须保留所需语义。匿名下载等场景应遵循原契约,不能自行伪造用户身份。
找不到原记录或业务文档与实现不一致时报告缺口,不能宣称已有覆盖,也不借规范整理擅自新增业务政策。
具体参考:[PromotionPortalAppServiceTest](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/service/PromotionPortalAppServiceTest.java)
的 `approvePromotion_recordsSelfReviewAuditDetailForSuperAdminSelfApproval`:除了调用成功,
还验证 `PROMOTION_APPROVE`、`PROMOTION_REQUEST`、目标 ID、操作者与 `selfReview` 明细。
[AuditLogService](../../../server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/audit/AuditLogService.java)
及其 [测试](../../../server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/audit/AuditLogServiceTest.java)
是公共记录入口,不要在各服务自行拼接另一套审计持久化方式。
反例:重构后 HTTP 仍返回 200,因而删除旧审计调用;或用一条 `log.info("deleted")` 替代业务审计。
验收:对受影响操作补/保留 action、目标、操作者与必要明细断言,检查拒绝/失败分支不会记录虚假的成功事件。
mock 的 `record` 断言只能证明调用参数;改动审计持久化或事务时还需验证实际提交/回滚结果。

View file

@ -28,3 +28,32 @@
[outbox测试](../../../server/skillhub-app/src/test/java/com/iflytek/skillhub/task/ScanTaskOutboxDispatcherTest.java)
用于检查调度行为。
这些是回归锚点,不意味着本次已运行或真实 Redis/Scanner 链路已验收。
## Python Scanner 本体
修改 [skillhub_scanner_app.py](../../../scanner/skillhub_scanner_app.py) 的并发限制、取消、超时、响应脱敏或临时目录时,
先读 [本体测试](../../../scanner/tests/test_skillhub_scanner_app.py),不只运行 Java consumer 测试。
| 场景 | 当前行为与可观察结果 | 测试名 |
|---|---|---|
| 扫描容量已满 | 新请求返回 503,`Retry-After: 30`,不启动额外扫描 | `test_excess_scan_is_rejected` |
| 客户端取消,但底层扫描尚未结束 | 容量仍被占用,新请求仍拒绝;原扫描结束后释放容量 | `test_client_disconnect_keeps_slot_until_scan_finishes` |
| 扫描超过硬超时 | 调用重启处理;真实实现记录 critical、关闭日志并退出进程 | `test_hard_timeout_requests_process_restart`(替换重启函数,未真正退出进程) |
| 响应 findings 含敏感文本 | 脱敏但保留原响应契约与正常文本 | `test_scan_findings_redact_credentials_without_changing_response_contract`、`test_safe_scan_findings_are_unchanged` |
| 初始化临时目录 | 仅重建私有运行目录,不清理父目录 | `test_import_recreates_private_runtime_temp_root_without_touching_parent` |
反例:HTTP 请求一取消就释放并发槽位,但底层线程仍扫描;再次重试会绕过容量限制。
沿用现有 shield/等待与 finally 释放路径;不要将客户端断开等同于计算已停止。
日志使用现有 `logging.getLogger(__name__)` 与参数化消息,新增日志不得包含 API key、上传包全文或未脱敏 findings。
当前包装层源码未配置 JSON formatter;不能把“结构化 JSON 日志”写成已具备的保证。
日志格式或容器退出恢复变化还需检查 [监控手册](../../../scanner/docs/monitoring-guide.md) 和运行配置。
仓库根目录运行现有 [CI](../../../.github/workflows/pr-scripts.yml) 使用的命令:
```bash
python3 -m unittest discover -s scanner/tests -p 'test_*.py'
```
期望所选用例全部通过。此套测试用替身隔离上游服务,不能证明 Cisco 扫描引擎、真实 HTTP 断连、容器重启或 Redis 链路已验收;
这些边界发生变化时按 [部署规范](../deployment/index.md) 补环境验证。

View file

@ -42,3 +42,23 @@ CI 另做多平台和 Node 产物运行,其实际必需检查不因本地按
[身份优先级](../../../cli/test/integration/auth-resolution.test.ts)、
[多进程并发](../../../cli/test/integration/concurrency.test.ts)。mock registry 的通过仅证明 CLI 消费契约,
不能标为真实服务端验收。公共 API 变化追加[后端接口契约](../backend/api-contracts.md)和[测试入口](../testing/index.md)。
### 例:修改参数错误或身份配置解析
| 改动 | 现有代表用例与结果 | 容易漏掉的验证 |
|---|---|---|
| 未知命令的 JSON 输出 | `runCli(['foo', '--json'])`:退出码 `5`,stderr 可解析为 `{ok: false, message: 'unknown command "foo" for "skillhub"', exitCode: 5}` | 只匹配错误文字会漏掉 JSON 前混入提示或退出码被改成 `0` |
| token 优先级 | fake registry 仅接受显式参数的假 token,环境变量放另一假值;`whoami` 应成功返回该 registry 的用户 | 只测 resolver,不能证明命令将参数传入了 resolver |
| registry 优先级 | 两个 fake registry 使用不同用户和 token;显式 registry 胜过环境变量,环境变量胜过 config | 不同 registry 返回相同 fixture 会掩盖请求发往错误服务的问题 |
上表来自链接的集成测试;新增 stdout 纯净性等断言时另行实现,不把表格当成所有检查都已有覆盖。
在已按 CLI 工具链安装依赖的环境,从仓库根目录执行局部反馈:
```bash
cd cli
bun run test test/integration/error-output.test.ts test/integration/auth-resolution.test.ts
```
`bun run test` 保留 package.json 的 pretest 元数据生成步骤;测试会派生本地 CLI 进程、
启动本地 fake HTTP registry 并使用临时配置,不应访问真实 registry。
此命令验证这两种行为,不能代替 CLI 行为改动所需的完整 lint/typecheck/test/build。

View file

@ -71,6 +71,38 @@ Flyway 是 schema 变更入口,应用容器启动会执行迁移;具体规
## 分层验收与收尾
### 例:修改 Web 子路径配置
本例说明如何落实“配置声明到实际使用”的检查;不是新的部署默认值。
涉及 `SKILLHUB_WEB_BASE_PATH`、同源 `SKILLHUB_WEB_API_BASE_URL` 或公开地址时,联读
[校验脚本](../../../scripts/validate-release-config.sh)、[Compose 透传](../../../compose.release.yml)、
[容器入口](../../../web/docker-entrypoint.d/20-base-path.sh)与[Nginx 模板](../../../web/nginx.conf.template)。
| 代表输入 | 应检查的结果 |
|---|---|
| Web base `/skillhub/`,同源 API base `/skillhub`,公开地址 `https://skillhub.example.com/skillhub` | 路径一致;生成带前缀的静态资源与 Nginx 路由 |
| Web base `/skillhub/`,同源 API base `/other` | 配置校验及运行入口拒绝,不静默使用另一 API 路径 |
| Web base `/api/` 或 `/foo/../bar/` | 拒绝保留前缀或点路径,避免遮蔽服务器路由或路径归一化后失配 |
| 根路径 `/` | 不生成重复子路径 location;静态资源保持根路径 |
不要只改 Compose 加一个变量就判定完成;也不能仅校验字符串末尾相同就接受公开地址的路径。
例如 `/other/skillhub` 与 `/skillhub` 不等价。独立 API 主机的绝对 URL 是允许的例外,不能套同源路径相等规则。
从仓库根目录运行已有代表检查:
```bash
sh scripts/tests/web-base-path-routing-test.sh
bash scripts/tests/validate-release-config-test.sh
```
前者使用临时 HTML 和配置文件检查容器入口生成结果,不启动 Nginx;后者使用假配置,
依赖 Docker Compose 的 `config` 渲染但不启动服务。它们均不是读取真实 `.env.release` 的交付校验,
也不能证明浏览器、OAuth 回调或反向代理实际可用。
需要容器 HTTP 验证时再读取[子路径 Smoke](../../../scripts/tests/web-base-path-nginx-smoke-test.sh),
核对它的镜像、容器创建与清理范围后执行。
### 证据层次
1. 静态配置和构建:证明配置结构、产物可构建,未证明部署可用。
2. 启动与健康:证明服务就绪、依赖连通,未证明权限和业务闭环。
3. 真实业务 Smoke:检查受影响入口、读写、权限拒绝与结果状态;结合

View file

@ -14,6 +14,31 @@
- Mutation 成功后按影响更新详情、列表、计数或权限缓存;检查当前筛选/分页是否仍成立。
错误处理遵守当前局部与全局处理边界,避免重复弹提示。
## 具体例子:列表排序与联动刷新
[usePromotionList](../../../web/src/features/promotion/use-promotion-list.ts)先把默认分页、状态与排序
归一化为 `normalizedParams`,同一个对象用于 `['promotions', normalizedParams]` 和 API 调用。
因此 `sortDirection: 'ASC'` 与 `'DESC'` 的结果不会共用一个缓存身份。
```ts
// 正例:请求条件与缓存身份使用同一份归一化参数
queryKey: ['promotions', normalizedParams],
queryFn: () => promotionApi.list(normalizedParams),
// 反例:请求已改变排序,键却不区分,可能复用另一排序的结果
queryKey: ['promotions'],
queryFn: () => promotionApi.list(normalizedParams),
```
同文件的审批/拒绝成功回调同时失效 `['promotions']` 和 `['governance']`,因为治理面板也消费
该结果。添加新的消费者时先定位其 query key;只刷新按钮所在列表可能让另一个页面保留旧状态。
这不是要求所有 mutation 都刷新这两个前缀,而是要求写出“受影响消费者 → 缓存键”的实际对应关系。
验证排序变更时分别断言 API 参数含 ASC/DESC、两个 query key 不相同;验证 mutation 变更时
断言实际受影响的缓存失效,不能只断言 mutation 请求成功。现有
[列表测试](../../../web/src/features/promotion/use-promotion-list.test.ts)覆盖前三者中的参数/键,
不覆盖 mutation 刷新或真实 QueryClient 竞态,按此次修改补足对应边界。
## 接口与行为契约
1. 快速切换 A→B 后,A 的迟到结果不能作为 B 显示或写入 B 的缓存。正确 query key 之外,

View file

@ -39,6 +39,7 @@
| 修改 API 消费、类型或生成 schema | [类型安全](./type-safety.md) |
| 修改架构、路由或共享模式 | [前端架构](../../../docs/08-frontend-architecture.md) |
| 涉及版本状态、权限或公开分发 | [生命周期契约](../../../docs/14-skill-lifecycle.md),并核对相关源码与测试 |
| 新增日志、错误诊断或排查凭证泄漏 | [前端验收:日志与诊断](./quality-guidelines.md#日志与诊断) |
| 组件交互、视觉与页面验收 | [前端验收](./quality-guidelines.md) |
## 验证与补充参考

View file

@ -22,6 +22,21 @@
- 取消/重试:只在此次改动涉及这些能力时验证其语义,不能用 signal 参数存在作为完成证据。
- 国际化:新增用户可见文字进入翻译资源,检查插值、空/长内容以及中英文布局;错误消息同样可翻译。
## 日志与诊断
- 需要记录的错误用 `console.error`,弃用提示用 `console.warn`;避免在生产代码遗留
`console.log` 调试输出。日志不能替代用户可见的失败提示,也不要在客户端和调用方重复记录同一错误。
- 禁止输出 token、Cookie、Authorization、密码及包含这些字段的完整请求/响应对象。
`console.error(error)` 也不是天然安全:先检查异常 message、cause 和附带上下文是否包含敏感数据。
- 需要定位请求失败时只选择已确认安全的字段,如操作名、HTTP 状态码;不要直接打印登录请求体、
token 创建结果或完整 URL 查询参数。示例:`console.error('Token creation failed', { status: 500 })`;
反例:`console.error('Token creation failed', response)`,后者可能带新生成的 token。
- 日志修改的验收:检查所有成功/失败分支的实际参数;涉及脱敏逻辑时用明显的假凭证标记驱动错误分支,
spy 日志输出并断言该标记未出现。不能用真实凭证做 fixture。
当前 [ESLint 配置](../../../web/.eslintrc.cjs)没有 `no-console` 规则;lint 通过不代表完成上述检查。
日志级别与字段限制需在变更 diff 中审查,本页不宣称已有自动脱敏或前端日志平台。
## 动效验收
涉及动效变化时,读取[交互动效规范入口](./motion-guidelines.md)及其正文,

View file

@ -12,6 +12,32 @@
- 后端 OpenAPI 契约变化后通过 `make generate-api` 生成类型,生成文件随契约修改交付;不手改生成结果。
命令实现以 [Makefile](../../../Makefile) 为准,检查前确认所用服务来自本次代码。
## 改接口字段时怎么落地
例如调整当前用户字段,先查看 [API 类型入口](../../../web/src/api/types.ts) 的 `User`,
再追到生成 schema 的 `AuthMeResponse`。当前 `User` 用 `Omit` 后重声明部分字段;这属于已有
适配边界,不是服务端自动保证所有字段必填的证据。新增必填假设应核对响应及缺失值处理。
```ts
// 消费既有类型,不在组件内复制一份 User 协议
import type { User } from '@/api/types'
type UserLabelProps = Pick<User, 'userId' | 'displayName'>
// 反例:用户身份可能包含非数字字符,转换会破坏身份
// const userId = Number(user.userId)
// 反例:断言只改变编译器看法,不能验证接口返回
// const user = response as User
```
新增测试数据可参考
[晋升列表 fixture](../../../web/src/features/promotion/use-promotion-list.test.ts) 的
`{ ... } satisfies PromotionTask`:缺字段时修复 fixture 或核实契约,不用 `as PromotionTask` 绕过检查。
`Partial<T>` 可表达真正的局部编辑状态,但不能用来掩盖最终提交协议所需字段。
字段变更后的检查顺序:核对后端响应/schema → 更新已有类型或适配 → 检查消费方和 fixture →
运行 `pnpm -C web typecheck` 与相关测试。仅类型变化不需要引入新的运行时校验库;
若此次需求涉及不可信输入,则在对应边界明确验证及失败行为。
## 现有边界
[客户端](../../../web/src/api/client.ts)同时包含 openapi-fetch 与手写 convenience wrapper,

View file

@ -25,7 +25,7 @@
| 改文案、补规范或小修 | 读相关规则,直接修改验证,不强制建任务 |
| 多步功能 | 任务记录保存需求、上下文、进度、验证;尊重用户已有任务创建选择 |
| 大型需求或架构调整 | OpenSpec明确需求设计,Trellis组织执行与接续 |
| 已有权威文档 | 入口引用,避免维护两份正文 |
| 已有权威文档 | 引用完整契约;关键规则可在 AGENTS 与专题重复,修改时检查一致性 |
| 换worktree | 从本机模板显式复用通用规则与工具配置,任务记录各自管理;已跟踪规范以分支内容为准 |
## 五类内容的职责
@ -39,12 +39,27 @@
| 思考指南 | 决策问题和规则链接,不暗中新增硬规则 |
五类是审查维度,不要求每篇填满,也不机械建立五套目录。
专题按独立适用条件拆分;同一规则只维护一份完整正文。规则需有适用范围、例外和源码/测试/设计来源。
专题按独立适用条件拆分;详细契约有明确归属,关键规则可在 AGENTS 和专题重复,变更时一并核对,不能出现互相冲突的版本。
规则需有适用范围、例外和源码/测试/设计来源。
代码现状不自动等于正确标准:分别标明目标、已知偏差和已验证行为,冲突不能静默裁定。
## 正文应具体到什么程度
索引负责选择,思考指南可以提出问题;实现专题不能只写“注意兼容”“合理测试”“复用现有能力”。
对本次新增或调整的规则,评审时应能回答:
- **何时读:** 哪种改动触发它,哪些情况不适用?例如仅改翻译文字不等于修改请求行为。
- **改哪里:** 指向真实类、函数、配置字段或测试;命名只是建议时明确标注,不虚构已有 helper。
- **怎么做:** 给出本项目的代表例子,必要时附反例;不把第三方项目的框架和业务规则照搬过来。
- **怎样判定:** 指出输入与可观察结果,例如错误退出码、缓存是否清空、数据库是否回滚,而非只列测试命令。
- **哪些尚未做到:** 将现有行为、目标要求和缺口分开;已存在测试不等于本次已运行,计划中的断言不写成已有覆盖。
简单命名规则不必填五段模板,复杂专题也不靠凑示例数量证明完整。新增命令应说明工作目录、依赖和副作用。
规则变化同时核对 AGENTS 中的摘要、所属索引和专题;链接检查只能证明路径可达,内容仍需对照来源审查。
## 与其他资料的关系
- AGENTS是精简读取入口与协作约束,领域规则在本目录。
- AGENTS直接保留关键产品、架构、安全与高频工程规则;本目录补充场景、例外、实现依据和验证方法。
- docs保留架构、业务语义和操作手册;OpenSpec保留正式需求、设计、行为变化与验收。
- POM、package.json、schema、源码、测试和脚本提供当前事实;规范链接它们,不维护重复版本清单。
- Trellis执行Skill只组织启动、读取、检查、收尾,不持有另一套项目规范。

View file

@ -28,5 +28,30 @@
## 来源与经验边界
[use-promotion-list.test.ts](../../../web/src/features/promotion/use-promotion-list.test.ts)
可参考参数、缓存键和失效断言,但其 mock 方式不证明 React Query 的真实竞态行为。
可参考参数与缓存键断言;目前未断言 mutation 的缓存失效,其 mock 方式也不证明 React Query 的真实竞态行为。
这是选择测试边界的例子,不要求所有 Hook 使用同一模板。
## 可直接执行的三个验证切片
以下命令从仓库根目录运行,依赖需已按锁文件安装。只选择与此次修改相关的切片,不因这里列出就全量执行。
| 改动 | 命令 | 通过时具体证明什么 |
|---|---|---|
| 晋升列表的分页/排序/key | `pnpm -C web test src/features/promotion/use-promotion-list.test.ts` | PENDING 默认参数、历史排序参数及 ASC/DESC 键区分 |
| 会话缓存清理 | `pnpm -C web test src/features/notification/notification-session.test.ts` | 用户键分区、指定私有缓存移除、公共搜索缓存保留 |
| 客户端路径/运行配置 | `pnpm -C web test src/api/client.test.ts` | 当前测试覆盖的 base URL 路径前缀、端点与运行配置行为;不证明取消或授权闭环 |
### 会话切换:为什么只断言“调用清理函数”不够
实际路径为 `useSessionBootstrap.onSuccess` → `clearSessionScopedQueries` →
`QueryClient.removeQueries`,随后才写入 `['auth', 'me']`。若新增私有查询却未纳入清理/分区机制,
清理函数照常被调用,仍可能留下上一用户的数据。
[现有缓存测试](../../../web/src/features/notification/notification-session.test.ts)使用真实 QueryClient:
先放入 A 的通知、标签和个人技能缓存,再清理,断言它们不存在,同时断言公共搜索缓存仍存在。
修改该机制时沿用这种“清理前有数据 → 清理后私有数据消失、公共数据保留”的正反断言,
并为新增缓存前缀补一项,而不是只检查 mock 函数调用次数。
当前测试使用已完成的缓存数据,不包含在途请求或真实登录流程。若改动涉及 A 请求尚未完成时
切到 B,另用受控 Promise 和真实 QueryClient 验证迟到的 A 数据不会成为 B 的结果。
不得把现有三个测试通过写成“账号切换、取消和竞态已全部验证”。

View file

@ -7,7 +7,7 @@
| 文案、小修或补规范 | 读取相关规则,直接修改验证,不强制建任务 |
| 多步功能 | 使用任务记录保存需求、上下文、进度、验证;尊重用户已有不创建任务的选择 |
| 大型需求或架构调整 | [OpenSpec](./openspec.md)明确正式需求设计,Trellis组织执行接续 |
| 已有权威文档 | 入口引用,不复制维护第二份 |
| 已有权威文档 | 引用详细契约,关键规则可在 AGENTS 重复;变更时核对入口与正文一致 |
| 换worktree | 从本机模板显式同步可复用规则及工具配置,任务各自管理;已跟踪spec以分支内容为准,模板更新必须比较后显式应用 |
## 实现与交接

View file

@ -21,12 +21,58 @@ Read [README](./README.md) for setup and [CONTRIBUTING](./CONTRIBUTING.md) for c
| Development, PR, completion | [Workflow](./.trellis/spec/workflow/index.md) |
| Reuse or cross-layer decisions | [Thinking guides](./.trellis/spec/guides/index.md) |
## Core project rules
- The registry is the core product; CLI and agent integrations are entry points.
- Preserve SKILL.md package compatibility and existing namespace/CLI contracts.
- Keep search and object storage behind replaceable interfaces.
- Authentication must remain extensible beyond a single identity provider.
- Changes to publishing, downloading, deletion or authorization must check
that the required audit trail is preserved.
## Backend essentials
- Controllers handle transport and trusted identity extraction; application
services orchestrate use cases; domain services own business rules.
- Put complex joins and presentation projections in app query repositories.
Domain services return domain objects, not app DTOs.
- Domain repository interfaces belong in domain; persistence adapters belong
in infra. Do not introduce new dependencies that violate module boundaries.
- The existing domain-to-storage dependency is a known deviation, not a
precedent for adding further cross-module dependencies.
- User identity is String throughout authentication, APIs and persistence.
This does not imply that every resource ID is String.
- Use existing localized domain exceptions and request-tracing facilities.
- Store API tokens using the existing secure hashing mechanism, never plaintext.
## Frontend essentials
- Use TanStack Query for server state and Zustand/local state for UI state.
Do not introduce useEffect-based API fetching.
- Reuse existing shared UI components, variants and cn() utilities.
- Use generated API types; do not hand-edit generated schema files.
- Keep user-facing text in translation resources.
- Use console.error for errors and console.warn for deprecation warnings;
avoid production console.log debugging and never log credentials.
## High-value checks
- For backend app tests, use make test-backend-app, or an equivalent Maven
invocation that includes -am, to avoid stale dependent-module artifacts.
- API contract changes must check generated types and affected consumers.
- Lifecycle changes must compare docs, source and tests; report disagreements
rather than silently changing business policy.
- Search changes: read docs/04-search-architecture.md.
- Business-flow changes: read docs/05-business-flows.md.
## Work proportionately
- Small edits: read relevant rules, edit and verify; do not require a task record.
- Multi-step work: use a task record for requirements, context, progress and verification, respecting an existing user choice not to create one.
- Large requirements or architecture changes: OpenSpec owns formal requirements and design; Trellis organizes execution and continuation.
- Reference existing authoritative documents instead of maintaining duplicate rules.
- Repeat critical rules here when they should be visible at session entry.
Keep detailed contracts in the relevant specs and authoritative documents.
When changing a repeated rule, check both locations for consistency.
- Each worktree owns its task records. Tracked specs follow the checked-out branch; local template updates are explicit and must not silently overwrite them.
## Verification and delivery