契约声明了 path 的 operation,interface 里没有对应路由 —— 「已 passing」推不出「这条路径今天能跑」
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 0
- Avg merge
- 1h 7m
- Merged PRs (30d)
- 969
Description
## 这是什么模式
**同一天撞到两次**,形态完全一样:
| feature | 状态 | 契约 | domain / application | infrastructure | HTTP 路由 | verification |
|---|---|---|---|---|---|---|
| **F68**(消息级评价) | `passing` | `rateMessage` 已签核 | `RatingRepositoryPort` 有声明 | **无实现** | **无**(`skill.controller.ts` 自己写着「不在 #459 范围内」) | 四条全在 `tests/capability/skill/`,**没有一条 import PgDatabase** |
| **F60**(Agent 行为审计 / 异常检测) | `passing` | `listAnomalies` / `markAnomalyNormal` / `queryOrgAudit` 已签核 | `domain/agent/anomaly-detection.ts` 判定函数完整(10 倍 / 24h,O-36) | **无表**(`grep -ril anomal apps/api/migrations/` → 0) | **无**(`grep -rn '"/anomalies' apps/api/src/interface/` → 0) | 只有它自己的测试 import 它,纯内存 |
⚠ **这两个都不是假 passing。** feature 边界确实画在 application / domain 层,验证也如实覆盖了那一层——「F60 passing」是真的,它验的那层真的成立,只是那层之下是空的。
问题在于**读的人推不出这件事**:`readiness` 队列、`doctor`、以及任何看 feature 状态做判断的地方,今天都会把这两条读成「这条能力可用了」。这正是 AGENTS.md 那条「静态痕迹 ≠ 动态事实」的一个新变种——**feature 状态本身就是一个静态痕迹**。
## 提议
一道机械检查:**契约里声明了 `path` 的 operation,如果它属于某个 `passing` feature 的范围,`apps/api/src/interface/` 里必须有对应路由**;没有就记一个可见缺口。
⚠ **不是「每个契约 operation 都必须有路由」**——那条判据是错的,见下面的实测数字。
## 实测(SHA `ec074433`,一次性勘探脚本,**不是**门控实现)
```
契约 operation 总数(带 method + path):483
interface 里找不到对应路由: 225
```
按契约文件分布(前几名):`interview.ts` 44、`agent-runtime.ts` 37、`files.ts` 22、`templates.ts` 21、`skills.ts` 20、`context-pack.ts` 9、`chat.ts` 7 …
**225 这个数字本身说明判据不能只看契约**:绝大多数是**合法的未实现**——契约先行的项目里,`not_started` 的 feature 当然只有契约没有路由,那是设计而不是缺陷。所以门控必须按 **feature 状态**取交集,而不是扫全部 operation。这也是这个 issue 不该顺手做进别的 PR 的原因:判据的边界本身需要想清楚。
## 反证种子(门控写出来必须当场抓到这四条,抓不到就是判据写松了)
```
POST /messages/:messageId/rating (F68,passing)
GET /anomalies (F60,passing)
POST /anomalies/:anomalyId/mark-normal (F60,passing)
GET /org-audit (F60,passing)
```
四条都已在勘探脚本的输出里确认命中;`apps/api/src/interface/` 全量拼接后 `includes("anomalies")` 与 `includes("org-audit")` 均为 **false**。
## 已知的难点(留给实现者,不要当成 TODO 照抄)
1. **operation ↔ feature 的映射今天不存在**。`feature_list.json` 有 `spec_ref`(指向 UC),契约有 `covers`(在 `design-signoff.md` 里指向 feature),但没有「这条 operation 属于哪个 feature」。可能得靠束级粒度近似:**某个契约束的所有 feature 都 passing,则该束的 operation 都该有路由**。
2. **路径字面量匹配是脆的**。NestJS 的 `@Controller("prefix")` + `@Get("sub")` 会把一条 path 拆成两处,纯字符串比对会误报。上面那个一次性脚本用了「路径静态片段全部出现过」的兜底,粗糙但够估数量级——**门控不能用这种判据**。
3. **别把它做成阻断门**。225 条里绝大多数是合法未实现;一上来就 FAIL 会逼人去关掉它。建议先做成 `doctor` 的 WARN + 一份可见缺口清单,等清单收敛了再考虑升级。
## 优先级
**P1,不阻塞任何在途工作。** 这是发现问题的登记,不是当下要做的活。
背景来源:[#1174](https://github.com/boardx/workspacex/pull/1174)(总览屏逐格上游勘探)里查 F60 时撞到,用的是查 F68 的同一套查法。coord-main 2026-08-14 批准单开条目。
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reading feature_list.json, the covers references in design-signoff.md, and the existing doctor checks around apps/api/src/interface/. Reproduce the exploratory counts at SHA ec074433, then define a feature-state-aware operation-to-route check that reports WARNs and a visible gap list, catches the four listed routes, and does not block on not_started features.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100