Puter 后端贡献指南:从架构分层到 API 演进规范,读懂 Puter 开源项目的协作工程实践
Puter(The Internet Computer)是一个免费、开源、可自托管的“浏览器里的桌面系统”,其后端采用 Controller–Service–Store–Client 分层架构。本文基于仓库根目录的贡献指南 CONTRIBUTING.md 全文展开,逐条解读其六条核心贡献规则与 PR 提交规范,并结合 doc/architecture.md、doc/contributing-apis.md、package.json 中的真实测试脚本与 src/backend/testUtil.ts 的测试环境实现,说明每条规则在代码层面如何落地验证。读完本文,你将知道在 Puter 后端提交一个合格 PR 需要满足哪些硬性条件、如何在本地跑通端到端测试,以及改动公共 API 时“文档、类型、测试同一 PR 提交”这一契约的完整工作流。
一、贡献总则:规则不强制,但让每个 PR 更容易合并
贡献指南开宗明义:
These rules aren't strictly enforced — but following them makes every PR easier.
即这些规则不会被机械卡死,但遵循它们能显著降低评审成本。对于后端新手,指南给出的第一个入口是 doc/architecture.md——它定义了 Puter 后端的分层、依赖注入方式与命名约定,是后续所有规则的“source of truth”。该架构文档明确指出,后端组织为一个层叠结构,每层只依赖其下层的模块,由入口类 PuterServer(src/backend/server.ts)按顺序实例化各层并通过构造函数向下传递实例:
HTTP 请求
→ Controllers 路由处理、输入校验、鉴权/限流门控、响应整形
→ Drivers 可选,/drivers/* 上的 RPC 处理器
→ Services 业务逻辑(假设调用方已鉴权)
→ Stores 持久化与领域模型封装
→ Clients sql / redis / s3 / dynamodb / email 等协议适配
→ Config config.*.json → IConfig
这个分层图决定了贡献规则里“Follow existing patterns”的具体含义:新代码必须放进正确的层,且不能跨层伸手(例如 Controller 不得直接调用 Client)。
二、规则一:Test it. Run it.——“能构建”不等于“能工作”
第一条规则要求:开 PR 之前,把受影响的代码路径端到端跑一遍;对新行为、新端点或 bug 修复要补测试;如果某处确实难以测试,要在 PR 描述里明说。
这条规则在 Puter 仓库中有明确的工程支撑——仓库自带一套完整的本地测试基础设施:
- 后端测试:
npm run test:backend(见 package.json 的scripts字段)等价于setupExtensions && build:workerLib && vitest run --config src/backend/vitest.config.ts,即先装配 extensions 目录、构建 worker 运行库,再用 Vitest 执行 src/backend/vitest.config.ts 配置下的全部后端测试。 - Postgres 变体:
npm run test:puterjs之外还有npm run test:backend:postgres,通过环境变量PUTER_TEST_DB_ENGINE=postgres切换数据库引擎,验证 MySQL/Postgres 双实现路径。 - 三平台 SDK 测试:
npm run test:puterjs:node/:browser/:workerd分别在 Node、浏览器、Cloudflare workerd 上跑同一套 puter-js API 测试套件(src/puter-js/tests/api/),贡献 API 文档明确要求“never write per-platform tests”,即一套用例三平台复用。
测试设施的核心是一个真实的内存服务器,而非大量 mock。src/backend/testUtil.ts 中导出的 setupPuterTestEnv 会:
- 分配一个真实临时端口(
allocateEphemeralPort()); - 使用
puter.localhost这样的真实域名而非127.0.0.1——因为 API 路由以api子域做门控,而*.localhost在现代平台上解析到回环地址; - 加载真实的 extensions 目录(客户端依赖
/whoami等落在扩展中的端点); - 预置 admin / user / otheruser 三个确定凭据的测试用户,测试代码可持预铸 token 或直接
POST /login认证。
也就是说,“跑通受影响的代码路径”在 Puter 语境下有明确操作定义:起一个带真实端口、真实扩展、真实凭据的服务器,把改动过的路由真实调用一遍。doc/contributing-apis.md 的测试章节同样把“prefer the in-memory test server (setupPuterTestEnv) over mocking”写成了惯例。
三、规则二:Follow existing patterns——与既有代码同形
第二条规则要求新代码与仓库中相似代码保持同构,分层、接线、命名以 doc/architecture.md 为准;若认为某个既有模式有问题,应当在 PR 或讨论中提出,而不是“悄悄偏离”。
该规则还包含一条少见的类型化约定,值得单独展开:
在纯 JS 文件中,鼓励使用 JSDoc
@type注解借用 TypeScript 类型系统,共享形状用@typedef;除非值是透传给上游层(由上游拥有其类型),否则不要把 API 表面标为unknown或未标注的...args。
这与仓库的 lint 体系一致:eslint.config.js 对 src/backend/**/*.ts 与 extensions/**/*.ts 启用 @typescript-eslint 的 flat/recommended 规则集并开启类型感知解析(projectService: { defaultProject: project }),对 src/backend 与 extensions 下的 JS 文件启用 js/recommended + Prettier,且 prettier/prettier 规则为 error——即格式与类型卫生是提交前可自动检查的部分。
架构文档中与“同形”直接相关的三条惯例可归纳为:
- TypeScript 优先:新代码可行范围内用 TS;已有 JS 文件可以在改动时“顺手转换”;
- 命名:变量/函数
camelCase,类与含类文件PascalCase(如AuthService.ts、KVStoreDriver.ts); - 去重:两个服务需要同一逻辑时,提升到 util/helper,而不是让同层服务横向调用彼此——“services should not depend on other services for code reuse”。
新增路由或 driver 时的“同形”标准在 doc/contributing-apis.md 中有代码级定义:Controller 继承 PuterController(src/backend/controllers/types.ts),用 @Controller(prefix) 类装饰器加 @Get/@Post 方法装饰器声明路由(src/backend/core/http/decorators.ts);Driver 继承 PuterDriver(src/backend/drivers/types.ts)并打上 @Driver(interfaceName, opts) 装饰器。核心代码在 src/backend/controllers/index.ts 与 src/backend/drivers/index.ts 注册,扩展则通过 extension.registerController(...) / extension.registerDriver(...) 接入。
四、规则三:Don't expose system or user information——把信息暴露当作 diff 审查项
第三条规则把“不泄露系统与用户信息”写成了对 diff 的扫描动作:检查是否有散落的日志、调试路由、内部路径、密钥、token 或错误响应/响应体中的用户数据;拿不准时,返回更少;涉及鉴权、权限或数据导出的改动要在 PR 描述中标注。
这条规则与 API 贡献流程中的“第 7 步:Security pass”完全对应(doc/contributing-apis.md):开 PR 前扫描 diff——错误/日志中不泄露内部细节、响应不过度宽泛、鉴权门控齐全,并将任何 auth/permission/data-export 相关改动在 PR 描述中显式标出。
从源码结构看,这条规则背后是一整套默认拒绝(default-deny)的门控机制:
- Controller 路由接受
RouteOptions(src/backend/core/http/types.ts):requireAuth、requireUserActor、adminOnly、allowedAppIds、subdomain路由、每路由rateLimit、body parser 等,且文档提示“auth flavors are subtle and default-deny — read the JSDoc on each field before picking”; - Driver 方法通过
@Driver选项声明 per-methodrateLimit、concurrent在途上限、requireSubscription、requireReputation、noUserSession,由/drivers/call统一执行; - 预算类门控
requireCredits: true会在 handler 执行前用 402 拒绝无预算的账户,实现位于 src/backend/services/metering/enforcement.ts。
此外,指南指向 SECURITY.md:私有安全漏洞不走公开 issue/PR/讨论,而是发邮件至官方安全邮箱,报告需附可复现步骤、明确影响与受影响版本号。对贡献者而言,这条边界意味着:发现疑似漏洞时,正确动作是走私报渠道,而不是在 PR 里公开讨论。
五、规则四:AI-assisted code is fine — understood code is required
第四条规则对 AI 辅助开发给出明确立场:可以使用,但不允许提交你自己写不出、调试不了、答辩不来的代码。要求是:读 diff、运行它、在 review 中能够解释它。
这是当前开源仓库中少见的、把“AI 生成代码的可理解性”写成明文规则的做法。结合规则一可以推断其评审含义:AI 生成的 PR 不会因为是 AI 写的被拒,但“我没有跑过端到端测试”“我解释不了这段重试逻辑为什么这样写”会成为拒绝理由——因为规则一已经要求“run the affected code path end-to-end”。
六、规则五:Adding or changing APIs——文档、类型、测试同一 PR 移动
第五条规则针对“新增或修改公共 API”(endpoint、driver 方法或 puter-js 方法):必须遵循 doc/contributing-apis.md,并且向后兼容性、开发者文档、类型声明、测试四项必须在同一个 PR 中完成——“backward compatibility, developer docs, types, and tests all move in the same PR”。
该 API 文档的第一原则是“Rule zero: don't break callers”。其背景约束非常具体:puter.js 以无版本锁定的方式在线分发,所有现存应用在变更部署的瞬间就会拿到新行为,因此“每一个可观察行为(参数处理、响应字段、错误码、顺序)都要假设有依赖者”。由此导出一组硬约束:
- 新增参数必须是可选的,默认值精确复现旧行为;
- 永不重命名、挪用或删除既有参数、响应字段、错误码;
- 可能让既有调用者意外的新行为必须放在 opt-in flag 后面;
- “Docs, types, and tests move in the same PR as the behavior. A signature change with stale docs is a bug”;
- 破坏性变更需要维护者显式签核 + 文档化的迁移路径 + 上线计划(先加新面、再弃旧面、很久以后才谈删除)。
新增 API 的七步流程(设计表面 → 后端 → puter.js → 类型 → 文档 → 测试 → 安全复查)中,对贡献者最有操作价值的几点:
- 设计先行:先定签名、选项、返回形状与错误用例,再找两三个最相似的既有 API 对齐约定;新参数/字段名用
camelCase;返回列表的 API 遵循分页约定(doc/pagination.md:limit/cursor进,{ items, cursor, total }出)。 - Controller 还是 Driver:需要细粒度控制(URL 形状、HTTP 动词、每路由门控、流式响应)选 Controller;一组 RPC 方法实现某个命名接口(
puter-kvstore、puter-chat-completion等)且通用 driver 管线够用,则选 Driver。 - 类型即文档源:在实现处写 JSDoc(
@param/@returns、每接受一种调用形式一个@overload、新形状用@typedef {Object}+@property),声明文件由 JSDoc 生成,无需手工同步;运行npm run check:puterjs:types验证发布面。src/puter-js/types/是 gitignore 的构建产物,永远不要手改;跨模块共享形状放 src/puter-js/src/lib/types.js。 - 文档页:在 src/docs/src/
<Area>/<method>.md添加方法页(frontmatter、语法、参数、返回值、至少一个可运行示例)并更新 Area 总览页。
限额变更的同 PR 义务
第五条规则还包含一段针对限额的专门条款,原文值得完整保留:
The same rule applies to limits: rate limits, concurrency caps, quotas, and allowances are published in src/docs/src/rate-limits-and-quotas.md, and a PR that changes one of those numbers updates that page in the same PR. A limit nobody published is a limit developers only discover as a service failure.
即:改任何速率限制、并发上限、配额或额度数字的 PR,必须在同一 PR 里更新 src/docs/src/rate-limits-and-quotas.md。该页面是开发者可见限额的唯一权威来源,其中按 paid / free / anonymous 三档列出 AI、KV、文件系统、Workers 等各接口的滚动窗口限额与并发数(如 AI 每 10 秒请求数 200/30/20,KV get/set 每 10 秒 400/400/200),并区分三类独立机制——usage credit(402 insufficient_funds)、rate limit(429 too_many_requests)、storage quota(413 storage_limit_reached)。
工程含义:在 Puter,限额不是“服务端内部常量”,而是对外契约的一部分。从源码结构看,限额执行散落在 driver 的 @Driver 策略(per-method rateLimit/concurrent)与 controller 的 RouteOptions.rateLimit 上,而数字的“发布”动作落在文档页——贡献流程强制把两者绑定在同一变更单元,避免出现“代码里限了 300,文档写着 400”的漂移。
七、规则六:Boy Scout Rule——让仓库比你来时好 1%
第六条规则借用“童子军规则”:修掉你路过的拼写错误、死导入、缺失的测试、让你读了两遍的代码片段;但清理必须与变更成比例——不要借 bug 修复之名单独夹带重构。
这条规则与规则二(Follow existing patterns)形成互补:规则二要求你“不要悄悄偏离既有模式”,规则六要求“不要为了纠正你认为错误的模式而扩大变更面”。两者合力界定了 Puter PR 的变更半径:一次 PR 只解决一件事,顺手的清理限级于本次已经打开的文件与逻辑。
八、开 PR 的四个清单项
贡献指南对 PR 本身的描述虽短但完整,四条如下:
| 要求 | 含义 |
|---|---|
| One thing per PR where possible | 一个 PR 只做一件事,降低评审与回滚成本 |
| Describe what and why; the diff shows how | PR 描述回答动机,不重复 diff 已呈现的实现细节 |
| Mention how you tested user-visible changes | 与规则一呼应:说明端到端验证方式 |
| Drafts welcome | 草稿 PR 被欢迎,早期对齐成本低于后期打回 |
再叠加 doc/contributing-apis.md 的 “Definition of done” 清单(向后兼容、选对核心/扩展归属、puter.js 约定 { message, code } 错误形状、类型与运行时行为一致、文档页与示例、后端 + 三平台 SDK 测试 + 桌面 UI 的 Playwright e2e、diff 安全复查、你亲自端到端跑过),一个 API 级 PR 的完成标准在仓库内是双重可核对的。
九、本地实操:把规则落成可执行的检查步骤
把以上规则翻译成贡献者的本地工作流,仓库给出的命令面是 package.json 的 scripts 与 Node >= 24 要求(engines.node: ">=24.0.0"):
# 1. 启动开发环境(GUI + 后端)
npm start # 等价于 node ./tools/start.mjs
# 2. 端到端验证受影响的代码路径(规则一)
npm run test:backend # 后端全量 Vitest
npm run test:puterjs:node # puter-js API 套件(Node 平台)
# 3. 类型面检查(规则二 / API 流程第 4 步)
npm run check:puterjs:types # 生成类型声明并做发布面类型检查
# 4. 完整构建(规则一:确认产物可构建)
npm run build
其中 test:backend 的完整命令是 npm run setupExtensions && npm run build:workerLib && vitest run --config src/backend/vitest.config.ts——注意它先执行 setupExtensions(tools/extensionSetup.mjs)装配 extensions/ 目录,这与架构文档中“extensions 与核心层栈平行、可注册进任意层”的设计一致。贡献指南引用的“好扩展”范例(extensions/thumbnails.ts、extensions/serverInfo.ts、extensions/devWatcher.ts)都是可选功能干净地挂载其上;而 extensions/whoami.ts 被架构文档点名为反面教材——一个所有已认证客户端都依赖它的“承重扩展”。对贡献者而言这是一个可操作的判断测试:“删掉它 Puter 还能不能工作?” 能,就放 extensions/;不能,就进核心层。
规则速查表
| 规则 | 一句话要求 | 仓库内的可核对证据 |
|---|---|---|
| 1. Test it. Run it. | PR 前端到端跑通受影响路径,新行为补测试 | package.json scripts、src/backend/testUtil.ts |
| 2. Follow existing patterns | 分层/命名/接线同形;JS 用 JSDoc 类型化 | doc/architecture.md、eslint.config.js |
| 3. Don't expose information | 扫描 diff 中的日志/路径/密钥/用户数据;拿不准就少返回 | SECURITY.md、src/backend/core/http/types.ts 门控 |
| 4. AI code ok, understood code required | 提交前读、跑、能在 review 中解释 | CONTRIBUTING.md 原文 |
| 5. API changes move as one unit | 兼容 + 文档 + 类型 + 测试同 PR;限额变更同 PR 更新限额页 | doc/contributing-apis.md、src/docs/src/rate-limits-and-quotas.md |
| 6. Boy Scout Rule | 顺手 1% 改进,拒绝夹带重构 | CONTRIBUTING.md 原文 |
十、小结
Puter 的贡献规范本质是一份“变更半径 + 证据义务”的约定:变更半径由规则二、六与“One thing per PR”划定;证据义务由规则一(端到端运行)、规则三(安全复查)、规则五(文档/类型/测试/限额同 PR 移动)承担。它不依赖 CI 卡点强迫执行,而是把 doc/architecture.md 的分层定义、doc/contributing-apis.md 的七步 API 流程、src/docs/src/rate-limits-and-quotas.md 的限额契约和 setupPuterTestEnv 这类可自证的工具链组合成一套可核对的标准——贡献者无论是否使用 AI 辅助,都能用同一组文件判断自己的 PR 是否达标。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00