Skip to content

docs(api): trial 配额两个数字与真实 0.8.8 不符;一处行号 pin 已漂到无关代码 - #810

Open
vansin wants to merge 2 commits into
mainfrom
docs/license-trial-limits
Open

docs(api): trial 配额两个数字与真实 0.8.8 不符;一处行号 pin 已漂到无关代码#810
vansin wants to merge 2 commits into
mainfrom
docs/license-trial-limits

Conversation

@vansin

@vansin vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

docs(api): 修正 trial 档的两个配额数字,并把漂掉的行号 pin 改成函数名

1. trial limits 有两个数字是错的

文档 GET /api/license 的 trial 样例写:

"limits": { "max_agents": 5, "max_networks": 1, "max_tasks_day": 100 }

真实 0.8.8(干净容器 bunx --bun @sleep2agi/commhub-server@0.8.8,全新库,type=trial):

"limits": { "max_agents": 5, "max_networks": 3, "max_tasks_day": 500 }

max_networks 1→3、max_tasks_day 100→500。源码侧确认不是环境差异 ——
server/src/db.ts 的 licenses 表 schema 默认就是:

max_agents INTEGER DEFAULT 5
max_networks INTEGER DEFAULT 3
max_tasks_day INTEGER DEFAULT 500

而 /api/license 直接读这张表(server.ts:888)。

这条会影响用户决策:照文档看会以为试用期只能建 1 个网络、每天 100 个任务。

2. 同页一处行号 pin 已经漂了

第 396 行(en 398)说 plan quota "在 auth.ts:184-189 enforced"。
实测 auth.ts:184-189 现在是发 user token 的代码,与配额无关;
真正的关卡在 createNetwork() 里(QUOTAS 表 + max_networks_owned 判定)。

根因是钉行号 —— 行号必然漂。改成钉函数名。
(RELEASE-SOP 里 R367 对 sdk-deep-dive.md 做过同样的动作:取消行号 pin。)

原文里「free plan 默认 max_networks_owned=2」核过,是对的,保留。

范围

只改 docs-site 的 zh + en 两个文件,不动产品源码。
探测全部在容器里做,生产只做了只读 curl。

## 1. trial limits 有两个数字是错的

文档 GET /api/license 的 trial 样例写:

  "limits": { "max_agents": 5, "max_networks": 1, "max_tasks_day": 100 }

真实 0.8.8(干净容器 bunx --bun @sleep2agi/commhub-server@0.8.8,全新库,type=trial):

  "limits": { "max_agents": 5, "max_networks": 3, "max_tasks_day": 500 }

max_networks 1→3、max_tasks_day 100→500。源码侧确认不是环境差异 ——
server/src/db.ts 的 licenses 表 schema 默认就是:

  max_agents    INTEGER DEFAULT 5
  max_networks  INTEGER DEFAULT 3
  max_tasks_day INTEGER DEFAULT 500

而 /api/license 直接读这张表(server.ts:888)。

这条会影响用户决策:照文档看会以为试用期只能建 1 个网络、每天 100 个任务。

## 2. 同页一处行号 pin 已经漂了

第 396 行(en 398)说 plan quota "在 auth.ts:184-189 enforced"。
实测 auth.ts:184-189 现在是**发 user token** 的代码,与配额无关;
真正的关卡在 createNetwork() 里(QUOTAS 表 + max_networks_owned 判定)。

根因是钉行号 —— 行号必然漂。改成钉函数名。
(RELEASE-SOP 里 R367 对 sdk-deep-dive.md 做过同样的动作:取消行号 pin。)

原文里「free plan 默认 max_networks_owned=2」核过,是对的,保留。

## 范围

只改 docs-site 的 zh + en 两个文件,不动产品源码。
探测全部在容器里做,生产只做了只读 curl。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

接受这条 MINOR —— 是我措辞过宽

审查意见指出中文的「当前 quota 仍在 createNetwork() 里 enforced」过宽,会把两层混成一层。这条成立,我认。

我的本意只覆盖建网门,但句子里 quota 是单数、无限定,读者完全可能理解成「trial 的 max_agents / max_networks / max_tasks_day 三个字段都在这个函数里 enforce」—— 而我没有验过后两者。en 那侧因为保留了 free max_networks_owned=2 反而不易误读,所以这是 zh/en 不对称,不是两边都错。

拟采纳的替换(来自审查意见,我未改动):

建网配额在 auth.tscreateNetwork() 按 plan 的 max_networks_owned 校验(free=2,admin 豁免);trial limits 默认见 licenses 的 5/3/500,与行号 184–189 发 token 无关。

现在不改分支 —— 本 PR 在冻结中(我此前反复改动导致复审无法收口,已停手)。这条记在这里,等复审收口后随其它意见一并落。

另外确认一条 NOT COVERED 的定性:作者(我)那句 0.8.8 干净容器动态探测,审查方判定「仅作者声明、无独立日志、不得当独立复验证据」是对的。这里数字的正确性应当只由 base 的 db.ts:695-700 默认值支撑,不该由我那次探测背书 —— 两者结论一致纯属应该如此,不构成互证。

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

自审(本 PR 至今零行内审查):把 5/3/500 这条链路从头验到尾,成立;顺带关掉一条 NOT COVERED

本 PR 是三条从没被自动审查器碰过的最后一条(#823 已自审出一条 fail-open 并修,#800 自审为无发现)。

文档 PR 的生死线就是数字对不对,所以直接走源码。

完整链路(三跳,全部命中)

① 建表默认值server/src/db.ts:698-700

max_agents    INTEGER DEFAULT 5,
max_networks  INTEGER DEFAULT 3,
max_tasks_day INTEGER DEFAULT 500,

② trial 插入不指定这三列server/src/db.ts:713

INSERT INTO licenses (id, license_key, type, expires_at)
VALUES (?1, ?2, 'trial', datetime('now', '+14 days'))

只有四列,三个配额列缺席 → 取①的默认值

(对照:server/src/server.ts:916pro 插入是显式给值 50, 10, 10000 —— 两条路径分开,不会互相污染。)

③ REST 原样回传server/src/server.ts:888:899

"SELECT type, expires_at, max_agents, max_networks, max_tasks_day FROM licenses ORDER BY created_at LIMIT 1"

limits: { max_agents: license.max_agents, max_networks: license.max_networks, max_tasks_day: license.max_tasks_day },

逐字段透传,没有任何变换、覆盖或兜底。

结论

文档里的 "limits": { "max_agents": 5, "max_networks": 3, "max_tasks_day": 500 } ,且是端到端可追的,不依赖任何运行时观察。

这关掉了一条 NOT COVERED

此前的窄审留了这条:

NOT COVERED:trial JSON 与 REST 响应路径、作者 0.8.8 动态探测(未复验)

现在 REST 响应路径这一支已验

值得说清楚的是用什么验的:不是重跑我自己那次 0.8.8 动态探测 —— 审查方判定「仅作者声明、无原始日志,不得当独立复验证据」是对的,而我再跑一次仍然是同一作者侧,不会变得更独立。走源码链路才是能独立复现的那条路:任何人拿这三个行号就能自己走一遍。

仍未覆盖(不夸大)

  • ORDER BY created_at LIMIT 1 取的是最早那行 license。文中数字描述的是全新安装的状态;若一台机器上后来激活过 pro,首行仍是 trial,但这属于超出本文档范围的情形;
  • createNetwork() 是否 enforce max_agents / max_tasks_day —— 仍未验。这也正是此前那条 MINOR(中文措辞过宽)指向的同一处,我已接受并记录,待收口后一并落。

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

最后一条 NOT COVERED 验完了,而结论比原来那条 MINOR 更重

此前留的是:

NOT COVERED:createNetwork 是否 enforce max_agents / max_tasks_day

验完:不但没 enforce,这三个数在整个仓里没有任何人 enforce。

createNetwork() 只认一个字段

// server/src/auth.ts:313-317
const QUOTAS: Record<string, { max_networks_owned: number; max_networks_joined: number }> = {
  free:  { max_networks_owned: 2,  max_networks_joined: 3 },
  pro:   { max_networks_owned: 10, max_networks_joined: 20 },
  admin: { max_networks_owned: Infinity, max_networks_joined: Infinity },

QUOTAS 只有两个字段,max_agents / max_tasks_day 根本不在这个结构里createNetwork()(:320)只比 max_networks_owned(:325-327)。

② 三个 license 字段全仓只被「读出来给人看」

引用点 做什么
server/src/server.ts:899 塞进 /api/license 响应
agent-network/bin/cli.ts:12819 打印:Soft limits: agents=…, networks=…, tasks/day=…

除此之外没有比较、没有拒绝、没有门。产品自己的 CLI 就把它们叫 Soft limits

(扫描范围:origin/main 全仓 *.ts / *.tsx,排除 *.test.*auth.ts 里那几处 max_networks另一个东西 —— plan QUOTAS 的 max_networks_owned,不是 licenses 表的列。)

所以原来那条 MINOR 说轻了

原判是「中文措辞过宽,易把 license trial 的 5/3/500 与 free plan 建网门 max_networks_owned=2 混成一层」。方向对,但实际情况更尖:

这两组数不只是"层不同",而是一组生效、一组根本不生效,而且:

  • license 说 max_networks: 3,plan 门实际按 max_networks_owned: 2 拒绝 —— 两个数不一样,读者按 3 去建第 3 个网络会被拒;
  • 文档把 "limits": {5, 3, 500} 摆在讲已生效配额门的段落旁边,读者几乎必然把它读成"这就是被 enforce 的限额"。

这跟文档自己已经点名的 max_members(dormant)是同一类,只是这三个还没被点名。

建议改法(比之前那版更准)

/api/licenselimits(trial 默认 max_agents=5 / max_networks=3 / max_tasks_day=500)是软限额:服务端只存储和返回,不做任何拦截(CLI 里也直接标作 Soft limits)。真正会拒绝请求的是 plan 配额 —— auth.tscreateNetwork()max_networks_owned 校验(free=2,admin 豁免)。注意两者的 networks 数字不同(3 vs 2),以实际生效的 plan 配额为准。

冻结中未改分支,与此前接受的那条 MINOR 一并待收口后落。


至此本 PR 的 NOT COVERED 只剩一条:ORDER BY created_at LIMIT 1 取最早那行 license,文中数字描述的是全新安装状态。这条我认为是文档范围的自然边界,不打算再收窄。

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

同一页上还有 4 个行号 pin 也漂了 —— 本 PR 修了一个,没扫同类

本 PR 的立意之一就是「原文钉的 auth.ts:184-189 已漂到发 token 的代码上,所以改钉函数名」。这个判断对。但同一个文件里还有 5 处 tools.ts:NNN 形式的 pin,逐个核完:全部漂了,一个都没命中。

文档位置 pin 声称那里有 origin/main 上实际是
rest.md:52 tools.ts:521 license_expired 仍 emit // discovery (#337 extracts this field). "host_supervisor" = (daemon role 的 zod 注释)
rest.md:1542 tools.ts:286 chained_reply network_id: z.string().max(200).optional(),
rest.md:1542 tools.ts:646 chained_reply cpu_pct: processCpuPct,
rest.md:1545 tools.ts:571 new_message/broadcast 实际 payload }],
rest.md:1545 tools.ts:911 同上 message_id: z.string().min(1).max(200),

真正的 license_expiredtools.ts:1265(if (license.expires_at < now) { … error: "license_expired" })。

其中一处特别值得指出来

rest.md:1545 的原句是:

旧 doc 在 new_message 上写过 message 字段、broadcast 上写过 {content, from} —— 都不对。verify [tools.ts:571 + 911] 实际 payload 以上表为准。

这句话的全部作用就是「别信旧文档,去这两个坐标自己核」—— 而这两个坐标核不了任何东西。 一条指向失效的验证指引,比没有指引更糟:它让人以为已经可以核实。

建议

按本 PR 已经确立的做法处理,不是更新数字,是改钉符号:

  • tools.ts:521 → 钉 license_expired 这个错误码本身(它是唯一的,grep -n 'license_expired' server/src/tools.ts 一步到位);
  • tools.ts:286/646 → 钉 chained_reply;
  • tools.ts:571 + 911 → 钉 new_message / broadcast 的构造点符号。

理由和本 PR 对 auth.ts 的处理完全一样:行号每次重构都会漂,符号不会。

一条对我自己的批评

本 PR 发现了「行号 pin 会漂」这个问题,却只修了触发它的那一处。修了一个实例、没扫同一类,而同页另外四处一直是坏的。

这跟我这两天反复在别人和自己代码里查的是同一件事:发现一个形态之后,必须查它是不是模式。我在 #815 的清单里写过这条,自己在这条 PR 上没做。

冻结中未改分支,与前两条(MINOR 措辞、软限额)一并待收口后落。

@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

本 PR 至今零审查记录(自动审查器只在开 PR / 草稿转就绪 / 显式 @ 时触发,push 不触发)。这是第二次请求。

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 147a9f0ff9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

这三条我在窄审后都写过"我认",然后一直停在"冻结中,待收口后落"。
而"收口"这个条件实际上从没到来 —— 我自己设的延后规则,变成了阻塞
自己修复的东西。本 PR 至今零审查记录,根本不存在会被打断的在途复审。

① 措辞过宽 + 软限额定性(两条合一处改):
   原文"当前 quota 仍在 createNetwork() 里 enforced"会把两层混成一层。
   实际更尖:/api/license 的 limits(trial 5/3/500)全仓无人 enforce ——
   唯一去处是塞进响应、以及 CLI 打印成 "Soft limits";真正会拒绝建网的
   是 plan 配额 max_networks_owned(free=2)。两者 networks 数字还不同
   (3 vs 2),读者按 3 去建第 3 个网络会被拒。中英两侧都改。

② 行号 pin:该页 tools.ts#L521 / #L286 / #L571 全部已漂(#L521 指到
   daemon role 的 zod 注释,真正的 license_expired 在别处)。按 #831 对
   参考页的结论,去掉误导的行号锚点 —— 链接指向文件是准的,而符号名
   (license_expired / chained_reply / payload)本来就写在句子里。
   中英各 3 处,共 6 处,改后残留 0。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

三条一直停在「已接受」的意见,现在落地了(147a9f0f → a89372f2)

先说为什么拖到现在 —— 这是我自己造成的

三条我在窄审后都写过「我认」,然后统一挂在「冻结中,待收口后落」。而**「收口」这个条件实际上从没到来**:本 PR 至今零审查记录,根本不存在会被打断的在途复审。

我设的延后规则,变成了阻塞我自己修复的东西。 而且它无声 —— 每次汇报里写「待收口后落」都像是在管理节奏,实际是九条同类意见(本 PR 3 条 + #803 3 条 + #801 3 条)在原地不动。

①② 措辞过宽 + 软限额定性(合并成一处改)

原文:「当前 quota 仍在 createNetwork() 里 enforced」。窄审说它把两层混成一层 —— 方向对,但实际更尖:

/api/license 的 limits(trial 5/3/500)  →  全仓无人 enforce
  server.ts   塞进响应
  cli.ts      打印成 "Soft limits"
  除此之外没有比较、没有拒绝、没有门

真正会拒绝建网的  →  plan 配额 max_networks_owned(free=2)

两者的 networks 数字还不同(3 vs 2) —— 读者按 3 去建第 3 个网络会被拒。中英两侧都补上了这个区分。

③ 行号 pin 全部已漂

该页的 tools.ts#L521 / #L286 / #L571 全部指不到声称的东西(#L521 指到 daemon role 的 zod 注释,真正的 license_expired 在别处)。

#831 对参考页的结论(钉符号不钉行号),去掉误导的行号锚点 —— 链接指向文件是准的,而符号名(license_expired / chained_reply / payload)本来就写在句子里。中英各 3 处,共 6 处,改后残留 0


一句

我这两天反复在写「接受一条意见和实施它是两件事」。这条 PR 是那句话的完整代价:三条正确的意见,在「已接受」状态里躺了整整一天。

剩下六条同类(#803 的 Bun 未钉死 / 安全套件顺序 / --rm 丢报告,#801 的 ②⑥⑤)我会按同样方式逐条落,不再等一个不会到来的「收口」。

vansin pushed a commit that referenced this pull request Aug 13, 2026
我把这条挂了两轮,理由都是"#809/#810 在改同一个文件,怕撞车"——但一直没真去核。
这轮核了,答案很具体:

  两个 rest.md 里的失效 pin 共 25 处
    与 #809/#810 的改动行段重叠 =  7 处   ← 本轮不动
    安全可改                   = 18 处

也就是说"怕撞车"只挡得住 28%,我却拿它当了两轮不做的理由。

撞车的 7 处(按 (文件,行) 精确排除,不是按文件跳过):
  zh 52 / en 54    tools.ts#L521   与 #810 的 49-55 / 51-57 重叠
  zh 1545 / en 1491 tools.ts#L571  与 #810 的 1539-1548 / 1485-1494 重叠
  zh 1547 / en 1493 push.ts#L38    同上
  en 398           auth.ts#L184    与 #810 的 395-401 重叠

改掉的 9 个唯一 pin(中英各一处,共 18):
  auth.ts#L7    → interface AuthUser
  auth.ts#L99   → invalid username or password(全仓 2 处)
  auth.ts#L102  → // User token (utok_) — not bound to network, …
  auth.ts#L243  → generateToken(全仓 3 处)
  auth.ts#L269  → network_id IS NULL AND token_id !=
  auth.ts#L374  → "auto-join", "full"
  db.ts#L168    → CREATE TABLE IF NOT EXISTS networks
  push.ts#L11   → const clients = new Map<string, SSEClient[]>()
  push.ts#L35   → { type: "connected", session: sessionName

替换按 (文件,行号) 定向,不是全局字符串替换 —— pre-pr-selfcheck §13 刚写完,
这次照做了。其中两条(push.ts:11 和 auth.ts:243)是复合标签
(`[`push.ts:11` `clients`]`),第一遍正则没匹配上,是靠"改了 7 处而不是 9 处"
这个数字对不上发现的 —— 也是 §13 说的那条经验。

连带:基线 14 → 5(门要求删 9 条);L0 预期 27/53 → 18/35。
剩下的 5 条正是上面 4 条撞车 pin 加 changelog 的 index.ts#L253。

验证(容器内,--network none):
  unique_pins=18  broken_pins=5  baseline_entries=5
  L6 anchors_checked=67  mismatches=0
  MUTATION_RED new-out-of-range-pin rc=1
  MUTATION_RED stale-baseline-entry rc=1
  MUTATION_RED broadcast-anchored-to-ack-inbox rc=1
  RESULT: PASS  退出码 0
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

提醒:这个 PR 与 #843(文档行号 pin 的下限门)有一处硬耦合

我在 #843 里加了 docs/doc-source-pins-baseline.txt,记录已知失效的行号 pin,语义是只许缩小:某条对应的引用从文档里消失了,门会要求把它从基线里删掉,不删就红。

这个 PR 恰好会让基线里的条目消失:

  • 删掉了引用 tools.ts#L521 / tools.ts#L571 / auth.ts#L184 的那几行

所以合并顺序上有一个动作项:

我在 #845 的树上模拟跑过,完整输出贴在 #843 的这条评论

不做的话,表现是「一个只改文档的 PR 把 CI 弄红了」,而红的原因看起来跟它毫无关系 —— 这条提醒的价值就在这里。

vansin pushed a commit that referenced this pull request Aug 13, 2026
上一轮我在 #843/#810/#834 上贴了一条跨 PR 耦合提醒:那两个 PR 一合,基线里
对应的条目会变 stale,门就红在「请从基线里删掉」。提醒是散文,执行的人还是
得自己去数该删哪几条 —— 把一个机械操作交给了记忆力。

这一轮把它变成一条命令:

  python3 scripts/check-doc-source-pins.py . --write-baseline

🔴 这个开关离「一键把门变绿」只差一个条件判断,所以它**只许缩小**:
   重算若会引入基线里没有的条目(= 出现了新的失效 pin),它拒绝写并退出非零。
   新失效该做的是把链接改对,不是追认进基线。

L7 两个方向都断言,不只测它能用:
  ① 干净树:不改写,报「基线已经是最新的」,且文件字节未动
  ② 注入一个新失效 pin:必须拒绝(rc≠0),且**确认基线没被写**
     —— 只断言"它红了"不够,要断言"它红了而且没写"
  ③ 造一个「引用消失」场景(把某条 pin 的引用改钉不可变 SHA):
     必须删对、条数变小、表头注释保留、门随后转绿

顺带在门红的提示里直接给出这条命令,不让人再去翻文档。

验证(容器内,--network none):
  [L7] ① 干净树:不改写,报「已是最新」
       MUTATION_RED write-baseline-refuses-new-failure rc=1
       ③ 引用消失时删对了(5 → 4),表头保留,门转绿
       复原后回绿 ✓
  RESULT: PASS  退出码 0

一处说明:这个改动逻辑上属于 #843 的脚本,但落在链尾(#845)。理由是 #843#844#845 是一条依赖链,改在链首要把两级重新合并重跑一遍;而三个 PR 是按序
合进 main 的,落在链尾到达 main 的时间相同。写在这里免得有人以为放错了地方。
@vansin

vansin commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

补充上一条提醒:现在不用手工数该删哪几条了 —— 合并后跑 python3 scripts/check-doc-source-pins.py . --write-baseline 即可,它只许缩小(出现新失效会拒绝写)。实现与断言见 #843 的这条评论

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.

2 participants