diff --git a/.trellis/spec/backend/error-handling.md b/.trellis/spec/backend/error-handling.md index b4027231..10e6fe55 100644 --- a/.trellis/spec/backend/error-handling.md +++ b/.trellis/spec/backend/error-handling.md @@ -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。 diff --git a/.trellis/spec/backend/index.md b/.trellis/spec/backend/index.md index b88d5618..a67360b2 100644 --- a/.trellis/spec/backend/index.md +++ b/.trellis/spec/backend/index.md @@ -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);列出实际调用链的数据库写入、外部副作用与失败返回 | + +上述是读取触发条件,不表示现有提供方已完成互换验证。涉及替换时按受影响行为验证,不凭“兼容协议”跳过验证。 diff --git a/.trellis/spec/backend/logging-guidelines.md b/.trellis/spec/backend/logging-guidelines.md index d46e5dc5..a016faa0 100644 --- a/.trellis/spec/backend/logging-guidelines.md +++ b/.trellis/spec/backend/logging-guidelines.md @@ -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` 断言只能证明调用参数;改动审计持久化或事务时还需验证实际提交/回滚结果。 diff --git a/.trellis/spec/backend/scanning-integration.md b/.trellis/spec/backend/scanning-integration.md index 523e714f..0c809b37 100644 --- a/.trellis/spec/backend/scanning-integration.md +++ b/.trellis/spec/backend/scanning-integration.md @@ -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) 补环境验证。 diff --git a/.trellis/spec/cli/index.md b/.trellis/spec/cli/index.md index b4717b01..34f23736 100644 --- a/.trellis/spec/cli/index.md +++ b/.trellis/spec/cli/index.md @@ -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。 diff --git a/.trellis/spec/deployment/index.md b/.trellis/spec/deployment/index.md index 6bba4b8d..de7270c3 100644 --- a/.trellis/spec/deployment/index.md +++ b/.trellis/spec/deployment/index.md @@ -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:检查受影响入口、读写、权限拒绝与结果状态;结合 diff --git a/.trellis/spec/frontend/hook-guidelines.md b/.trellis/spec/frontend/hook-guidelines.md index 9338d2fa..b6d25395 100644 --- a/.trellis/spec/frontend/hook-guidelines.md +++ b/.trellis/spec/frontend/hook-guidelines.md @@ -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 之外, diff --git a/.trellis/spec/frontend/index.md b/.trellis/spec/frontend/index.md index ce9e1868..ef8a07e1 100644 --- a/.trellis/spec/frontend/index.md +++ b/.trellis/spec/frontend/index.md @@ -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) | ## 验证与补充参考 diff --git a/.trellis/spec/frontend/quality-guidelines.md b/.trellis/spec/frontend/quality-guidelines.md index 47159412..02a49dfa 100644 --- a/.trellis/spec/frontend/quality-guidelines.md +++ b/.trellis/spec/frontend/quality-guidelines.md @@ -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)及其正文, diff --git a/.trellis/spec/frontend/type-safety.md b/.trellis/spec/frontend/type-safety.md index 952c17f2..0af8d55a 100644 --- a/.trellis/spec/frontend/type-safety.md +++ b/.trellis/spec/frontend/type-safety.md @@ -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 + +// 反例:用户身份可能包含非数字字符,转换会破坏身份 +// 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` 可表达真正的局部编辑状态,但不能用来掩盖最终提交协议所需字段。 + +字段变更后的检查顺序:核对后端响应/schema → 更新已有类型或适配 → 检查消费方和 fixture → +运行 `pnpm -C web typecheck` 与相关测试。仅类型变化不需要引入新的运行时校验库; +若此次需求涉及不可信输入,则在对应边界明确验证及失败行为。 + ## 现有边界 [客户端](../../../web/src/api/client.ts)同时包含 openapi-fetch 与手写 convenience wrapper, diff --git a/.trellis/spec/index.md b/.trellis/spec/index.md index 9e68009e..067464b2 100644 --- a/.trellis/spec/index.md +++ b/.trellis/spec/index.md @@ -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只组织启动、读取、检查、收尾,不持有另一套项目规范。 diff --git a/.trellis/spec/testing/frontend-testing.md b/.trellis/spec/testing/frontend-testing.md index 87c62864..9d4b9619 100644 --- a/.trellis/spec/testing/frontend-testing.md +++ b/.trellis/spec/testing/frontend-testing.md @@ -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 的结果。 +不得把现有三个测试通过写成“账号切换、取消和竞态已全部验证”。 diff --git a/.trellis/spec/workflow/development-flow.md b/.trellis/spec/workflow/development-flow.md index e0759a71..6e938d58 100644 --- a/.trellis/spec/workflow/development-flow.md +++ b/.trellis/spec/workflow/development-flow.md @@ -7,7 +7,7 @@ | 文案、小修或补规范 | 读取相关规则,直接修改验证,不强制建任务 | | 多步功能 | 使用任务记录保存需求、上下文、进度、验证;尊重用户已有不创建任务的选择 | | 大型需求或架构调整 | [OpenSpec](./openspec.md)明确正式需求设计,Trellis组织执行接续 | -| 已有权威文档 | 入口引用,不复制维护第二份 | +| 已有权威文档 | 引用详细契约,关键规则可在 AGENTS 重复;变更时核对入口与正文一致 | | 换worktree | 从本机模板显式同步可复用规则及工具配置,任务各自管理;已跟踪spec以分支内容为准,模板更新必须比较后显式应用 | ## 实现与交接 diff --git a/AGENTS.md b/AGENTS.md index cc149b67..cd5c226a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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