Skip to content

docs(contract): 落地 round-73 已裁决的契约语义——owner 取值域闭合(Runner 退役)/ phase 适用范围 / Mobile 单一口径 / pageSize 夹取语义,并收敛分页路径最后一处手写 clamp(ADR-030+ADR-031,#2258 #2243) - #2305

Merged
DeliciousBuding merged 2 commits into
masterfrom
docs/contract-semantics-decided
Sep 3, 2026

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

docs(contract): 把 round-73 已裁决的契约语义落进权威文件——owner 取值域闭合(Runner 退役,4 个 /v1/workspaces 改 Hub 且保持 planned)、phase 适用范围声明、Mobile 单一口径、pageSize 夹取语义文档化;并收敛分页路径上最后一处手写 clamp(ADR-030 / ADR-031,#2258 #2243

裁决已在 docs/decisions.md(ADR-030/031,#2303);本发是它们的实施写集。四个文件族:

1. api/openapi.yaml — 4 处 x-agenthub-owner: RunnerHub(全部保持
status: planned:翻成 implemented 会被 verify-openapi-contract.py 拉进 router
比对,而 router 没有 /v1/workspaces/* ⇒ 必红;脚本内已断言这 4 处的 status)。
实测验证:verifier 输出仍是 153 / 156 / 3 / 0,与本 issue 记录的基线逐字相同。
共享 PageSize 参数补 description + 就地注释,写明「超过声明上限即夹到该上限、
不报错、不回落默认值、用 nextCursor 续取」。

2. api/conventions.md§Pagination 补夹取语义(含三族上限的实测口径:
通用 200 / 消息通知 100 / run·team-event·文档 500,以及「13 个 handler 回传游标、
另两个 clamp 端点是 limit/offset 形态」);§OpenAPI Metadata 声明两个取值域闭合
owner ∈ {Hub, Edge}Runner 退役的理由与证据、phase 只适用 /v1/** 设计面)。
该文件有 190 行门禁(verify-doc-ssot.py DOC-MAX-LINES),第一版写到 194 行判红
⇒ 压缩到 189 行通过,没有为了塞进新内容去抬门禁预算

3. api/README.md — 模块边界表把 Workspace 从 Edge 行移到 Hub 行(与实现位置
一致:hub-server 的 handler/service/repository/model + router 以 /web/projects* 暴露),
消掉「照 owner 找归属会去 edge-server/internal/runners/ 而那里只有 registry.go」这条
分叉诱因;§阶段标记适用范围(166/284 缺 phase 不是覆盖率缺陷,实测缺失分布
/web 91·/client 64·/edge 5·/v1 3·/health·/api·/cloud 各 1),并如实登记
3 个 /v1 例外
GET /v1/metricsGET /v1/agent-instancesPOST /v1/permissions/decide):
docs/architecture/ 全文没给它们定过 P0~P4、同族兄弟端点 phase 也是 P0/P1/P2/P4 混杂无法
照抄 ⇒ 只声明规则、不代产品填值(填一个没有出处的 phase 等于制造下一条「文档与实况
分岔」),补齐需 owner 给值,跟踪在 #2258。这是对 ADR-030 原文「只补真正违反的 3 个」的
一处如实修正:查过出处之后发现无据可填。

4. README.md / README_EN.md — Mobile 口径统一到更强的那一句(「装配中的
fixture/边界验证 lane,非 release candidate」),并给出可机械验证的锚点:
release.yml:383 build-mobileRELEASE_MOBILE_ENABLED 门控默认 skipped、
:453/:487 对 skipped 做了条件跳过 ⇒ 发布链路事实上不出 mobile 产物。zh/en 语义一致。

5. Go:分页路径上最后一处手写 clamprepository/message.go GetMessagesIncrement
if limit <= 0 || limit > Max { limit = Max } 改为
config.ClampPageSize(limit, MaxIncrementalMessageLimit, MaxIncrementalMessageLimit)
这正是 paging.go 自己写明的「端点把 0 当作 no explicit limit 而非请求默认值时,把
requested 值当 def 传」的既有惯例(同 handler/agent.go runEventFilterFromQuery)。
行为逐字不变(<=0 与 >max 都落到上限),变的是它不再是私有副本;paging.go
「唯一残留手写分支」那段注释随之改成如实口径(并解释为什么它看起来像违规其实是那条
escape hatch)。

门禁:verify-openapi-contract.py 153/156/3/0(与基线同)、verify-doc-ssot.py ok
(含 conventions.md 189/190 行预算与 AGENTS.md 路径检查)、verify-conventions.py
Passed 1 Failed 0、gofmt -l 空、go build ./... OK、go test ./internal/config/
./internal/repository/ ./internal/handler/ 全绿。

Refs #2258 #2243(两条各自剩下的实施项由本发关闭;#2258 仅余「3 个 /v1 phase 取值」
需产品给值,已在 api/README.md 就地登记)

Co-authored-by: Cursor cursor@vectorcontrol.tech

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 652b7630-29f7-4e67-bff6-e8f1d072b0a4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…/v1/workspaces 改 Hub 且保持 planned)、phase 适用范围声明、Mobile 单一口径、pageSize 夹取语义文档化;并收敛分页路径上最后一处手写 clamp(ADR-030 / ADR-031,#2258 #2243)

裁决已在 docs/decisions.md(ADR-030/031,#2303);本发是它们的实施写集。四个文件族:

**1. `api/openapi.yaml`** — 4 处 `x-agenthub-owner: Runner` → `Hub`(全部保持
`status: planned`:翻成 implemented 会被 `verify-openapi-contract.py` 拉进 router
比对,而 router 没有 `/v1/workspaces/*` ⇒ 必红;脚本内已断言这 4 处的 status)。
实测验证:verifier 输出仍是 `153 / 156 / 3 / 0`,与本 issue 记录的基线逐字相同。
共享 `PageSize` 参数补 `description` + 就地注释,写明「超过声明上限即夹到该上限、
不报错、不回落默认值、用 nextCursor 续取」。

**2. `api/conventions.md`** — `§Pagination` 补夹取语义(含三族上限的实测口径:
通用 200 / 消息通知 100 / run·team-event·文档 500,以及「13 个 handler 回传游标、
另两个 clamp 端点是 limit/offset 形态」);`§OpenAPI Metadata` 声明两个取值域**闭合**
(`owner ∈ {Hub, Edge}` 且 `Runner` 退役的理由与证据、`phase` 只适用 `/v1/**` 设计面)。
该文件有 190 行门禁(`verify-doc-ssot.py` DOC-MAX-LINES),第一版写到 194 行判红
⇒ 压缩到 **189 行**通过,**没有为了塞进新内容去抬门禁预算**。

**3. `api/README.md`** — 模块边界表把 `Workspace` 从 Edge 行移到 Hub 行(与实现位置
一致:hub-server 的 handler/service/repository/model + router 以 `/web/projects*` 暴露),
消掉「照 owner 找归属会去 `edge-server/internal/runners/` 而那里只有 registry.go」这条
分叉诱因;`§阶段标记` 补**适用范围**(166/284 缺 phase 不是覆盖率缺陷,实测缺失分布
`/web 91`·`/client 64`·`/edge 5`·`/v1 3`·`/health`·`/api`·`/cloud` 各 1),并**如实登记
3 个 `/v1` 例外**(`GET /v1/metrics`、`GET /v1/agent-instances`、`POST /v1/permissions/decide`):
`docs/architecture/` 全文没给它们定过 P0~P4、同族兄弟端点 phase 也是 P0/P1/P2/P4 混杂无法
照抄 ⇒ **只声明规则、不代产品填值**(填一个没有出处的 phase 等于制造下一条「文档与实况
分岔」),补齐需 owner 给值,跟踪在 #2258。这是对 ADR-030 原文「只补真正违反的 3 个」的
一处如实修正:查过出处之后发现无据可填。

**4. `README.md` / `README_EN.md`** — Mobile 口径统一到**更强的那一句**(「装配中的
fixture/边界验证 lane,非 release candidate」),并给出可机械验证的锚点:
`release.yml:383 build-mobile` 受 `RELEASE_MOBILE_ENABLED` 门控默认 skipped、
`:453`/`:487` 对 skipped 做了条件跳过 ⇒ 发布链路事实上不出 mobile 产物。zh/en 语义一致。

**5. Go:分页路径上最后一处手写 clamp** — `repository/message.go GetMessagesIncrement`
的 `if limit <= 0 || limit > Max { limit = Max }` 改为
`config.ClampPageSize(limit, MaxIncrementalMessageLimit, MaxIncrementalMessageLimit)`:
这正是 `paging.go` 自己写明的「端点把 0 当作 no explicit limit 而非请求默认值时,把
requested 值当 def 传」的既有惯例(同 `handler/agent.go runEventFilterFromQuery`)。
**行为逐字不变**(<=0 与 >max 都落到上限),变的是它不再是私有副本;`paging.go` 里
「唯一残留手写分支」那段注释随之改成如实口径(并解释为什么它看起来像违规其实是那条
escape hatch)。

门禁:`verify-openapi-contract.py` 153/156/3/0(与基线同)、`verify-doc-ssot.py` ok
(含 conventions.md 189/190 行预算与 AGENTS.md 路径检查)、`verify-conventions.py`
Passed 1 Failed 0、`gofmt -l` 空、`go build ./...` OK、`go test ./internal/config/`
与 `./internal/repository/ ./internal/handler/` 全绿。

Refs #2258 #2243(两条各自剩下的实施项由本发关闭;#2258 仅余「3 个 /v1 phase 取值」
需产品给值,已在 api/README.md 就地登记)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
@DeliciousBuding
DeliciousBuding force-pushed the docs/contract-semantics-decided branch from 212f8af to 5b8ff94 Compare September 3, 2026 20:13
…成「co-absent」而通过,Mobile 这一条从此静默失效

上一发把 README zh/en 的 Mobile 口径改成「装配中的 fixture/边界验证 lane,非 release
candidate」,但 `verify-doc-ssot.py:400` 的成对标记仍是旧字面量
`("Mobile 装配中", "Mobile in assembly")`。门禁语义是「每对标记必须**同时出现或同时
不出现**」⇒ 两侧旧字面量都被我改掉之后,它变成了**同时不出现**,于是判过 ——
Mobile 这条 parity 检查就此空转:今后任何一侧单独改 Mobile 成熟度口径都不会再红。
这是「改文档时顺手把门禁弄成 vacuous」的典型,本地复跑 `validate` 才暴露
(CI 的 validate 判红,`PASS=60 FAIL=1`,失败点是该门禁的**负向自测**
`verify-doc-entrypoints.Tests.py::assert_readme_maturity_parity`,它的前置条件正是
README 里存在 zh 标记)。

修法(不是放宽,是让门禁继续咬住新口径):

- 成对标记改为 `("Mobile 是装配中的", "Mobile is an in-assembly")`。两个新标记在各自
  README 里都**唯一**(`grep -c` = 1 / 1),且都紧贴成熟度断言本身 ⇒ 一侧把
  「装配中」改成「已就绪」就会造成单侧缺失而判红,与旧标记的判别力等价。
- 同一对里的第二条 `("Desktop/Web 主线", "Desktop/Web are the mainline")` 未受影响
  (两侧都仍在),保持不动。
- 负向自测的 fixture 前置条件与 zh-only 变异串同步更新(`"Mobile 是装配中的"` →
  `"Mobile 是已就绪的"`)。**这是本发唯一改到的既有断言**,改的是它钉的字面量而不是
  它断言的行为:自测的职责仍然是「只改 zh 一侧的成熟度口径必须判 DOC-README-PARITY」,
  改完实测仍 `OK`(即变异仍被抓)。

门禁:`python3 scripts/verify/verify-doc-ssot.py` → `doc SSOT ok`(含 29 个自测接线检查);
`python3 scripts/verify/tests/verify-doc-entrypoints.Tests.py` → `OK`;本地复跑 `validate`
结果见 PR。

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
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