> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 设计原则与代码约定

> Sans-IO、Scoped Resource Context、Occam's Razor 三条原则如何变成可检查的代码约束，以及 core / adapters / app / api 各层允许做什么。

BlockX 仓库用三条设计原则约束 spec 和代码。它们不是抽象口号：仓库的 `AGENTS.md` 和 `docs/specs/code-style.md` 把它们展开成了具体的模块边界、依赖规则、错误语义和时间语义。这一页是这两份文件的导读，帮你在写代码前知道"什么会被 review 打回来"。

## 三条原则

<AccordionGroup>
  <Accordion title="Sans-IO：默认规则">
    新子系统先定义纯内存 core，用显式事件和命令描述行为，再在外面包 adapter。core 只维护状态机、幂等判定、阶段推进和结果收敛；RPC、UDS、etcd、数据库、时钟、日志、指标全部留在 adapter。

    仓库里的 `WorkerCore`（`internal/worker/core/`）、`CoordinatorCore`（`internal/coordinator/core/`）、`DispatcherCore`（`internal/worker/core/dispatcher.go`）都是这种形态。它们是代码边界，不是新增进程或角色。
  </Accordion>

  <Accordion title="Scoped Resource Context：资源治理型子系统的规则">
    IO 访问这类以"谁拥有资源、什么时候释放"为中心的模块，不强行拆成事件/命令流。它们显式建模 Worker 级共享作用域和 task 级局部作用域，把 quota、cache、singleflight、deadline 和清理绑定到作用域生命周期上。作用域退出是结构性清理边界，不依赖"稍后再发一条释放命令"。

    `internal/io/` 是这种形态，入口是 `WorkerIOScope` / `TaskIOScope` 这样的同步接口。
  </Accordion>

  <Accordion title="Occam's Razor：同时约束 spec 和代码">
    如无必要，勿增实体。新增 struct、interface、错误码、队列、状态或服务前，先问现有概念能不能复用或简化。如果一个新实体说不清"为什么现有 `module / core / adapters / scope` 边界装不下它"，先不要加。
  </Accordion>
</AccordionGroup>

判断用哪条原则的经验法则来自 `AGENTS.md`：如果一个行为是生命周期重的（slot、task、call 的状态推进），用显式状态、事件、命令表达；如果它是资源治理重的（连接池、配额、缓存），用显式作用域拥有权、获取和清理规则表达；如果两种框架都给不出一组小而可枚举的规则，说明设计还太模糊。

## 分层与依赖规则

| 目录                             | 允许                                                        | 禁止                                                                                              |
| ------------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `cmd/<bin>/`                   | 组装 `app.Profile`、调用 `app.Run`                             | 领域判断、准入策略、协议语义分支                                                                                |
| `internal/<module>/core/`      | 状态机、幂等、阶段推进、结果收敛、纯时间判定                                    | 依赖 gRPC / etcd client / DB client / 日志实现 / 指标实现；自己取当前时间、生成随机值、启动 goroutine；使用 `context.Context` |
| `internal/<module>/adapters/`  | transport、存储、时钟、日志、指标、并发；把外部输入翻译成 core 事件，把 core 命令翻译成副作用 | 复制一份 core 的状态判定；直接跨层修改 core 内部对象                                                                |
| `internal/<module>/app/`       | 装配 core 与 adapters、加载配置、定义关闭顺序                            | 领域规则                                                                                            |
| `internal/io/`（scope-oriented） | 作用域内使用 `context`、`defer`、信号量、同步 singleflight              | 让调度层、hook 层和 backend adapter 各自维护影子计数器                                                          |
| `api/`                         | 跨模块共享的协议结构、消息包络、字段约定、无副作用的校验和编码辅助                         | transport handler、存储模型、领域状态机                                                                    |

不要引入没有明确边界的 `pkg/utils`、`common`、`shared` 汇总目录。仓库里现有的 `internal/common/` 只放 `argspool`、`clientid`、`types`、`unsafebytes` 这几个有明确用途的小包，不是通用杂物箱。

## adapter 与 core 的交互模式

推荐"输入事件 → core 决策 → adapter 执行命令"的单向模式，而不是让 core 持有一组反向 callback。`docs/specs/code-style.md` 给的示意：

```go theme={null}
decision := workerCore.HandleRequestTaskSlot(core.RequestTaskSlotInput{
    TaskID: taskID,
    TTL:    ttl,
    Now:    now,
})

if decision.Err != nil {
    return writeRPCError(decision.Err)
}

for _, cmd := range decision.Commands {
    switch c := cmd.(type) {
    case core.ReturnSlotReservation:
        return writeRPCResult(c)
    case core.ReleaseSlot:
        slotStore.Release(c.SlotID)
    }
}
```

定时器、Executor、Plugin 或 IO 回调完成后，adapter 把结果重新包装成 core 事件再喂回去。这样 core 保持单线程、可重放、可表驱动测试。

具体的事件名和命令名以各组件页为准：[Worker](/components/worker)、[Coordinator](/components/coordinator)、[Call 执行子系统](/components/call-execution)。

## interface 使用

* interface 定义在真正的消费侧。
* adapter 调用 core 时通常不需要再为 core 抽 interface，直接依赖 `WorkerCore`、`CoordinatorCore` 等具体类型。
* 只有当 core 确实需要一个同步 port，且这个 port 能保持纯、窄、可替换时，才在 `core/` 内定义最小 interface 由 adapter 实现。
* 没有第二个实现或明确测试替身需求前，不要为"以后可能扩展"提前抽接口。

## 命名与建模

* 包名短、稳定、领域化，优先名词。
* 导出标识符用明确名字，避免 `Manager`、`Util`、`Data`、`Info` 这类弱语义后缀。
* 状态、事件、命令直接反映领域语义，例如 `TaskPhaseFinished`、`PrepareTaskActivation`、`ReleaseSlot`。
* 优先显式 struct 和命名枚举，不滥用 `map[string]any`、`any` 或零散字符串字面量。
* 需要多个布尔字段组合才能理解对象状态时，改成显式状态枚举。
* `NewXxx` 只在能建立不变量时使用；字面量包装不必强行写构造器。

## 错误与结果表达

* 先区分"准入 / 协议错误"和"已进入执行后的终态结果"，不要混成一套错误返回。在 Worker 上，边界是 slot 是否已从 `ALLOCATED` 激活到 `RUNNING`：激活前的错误是提交 / 准入层错误，激活后的失败统一进入 `TaskResult`。
* 稳定语义依赖错误码、枚举或命名结果类型，不依赖错误字符串。
* 对外协议已有稳定错误码约定时以协议字段为准，不在 adapter 内再发明平行语义。
* 在 Sans-IO core 里，预期业务结果建模成显式结果，不滥用 `error`。
* `TaskResult`、`InvalidSlot`、`NoSlot`、`Unavailable` 等边界必须与 `docs/specs/architecture.md`、`docs/specs/worker.md` 一致。

## 时间语义

* 时间语义集中建模，不散落在 handler、Plugin、driver 各自判断。
* Worker 侧的 slot TTL、task deadline、结果保留窗口收敛在 Worker 模块及其 timer adapter，以 `docs/specs/worker.md` 为准。
* Coordinator 侧的视图新鲜度、候选重试和选址相关时间语义收敛在 Coordinator 模块，以 `docs/specs/task-resource-coordinator.md` 为准。
* 在 scope-oriented 模块里，生命周期结束优先靠 scope `Close()`、context 取消和结构性 cleanup 保证，再辅以迟到结果防御。
* goroutine 的创建、退出和回收路径必须清楚，不能把生命周期管理留给进程退出兜底。

## 测试与文档同步

* `core/` 测试重点覆盖状态迁移、幂等、TTL、deadline 和失败收敛，优先表驱动。
* scope-oriented 模块测试重点覆盖 quota 获取 / 释放、scope 关闭、迟到结果丢弃、singleflight 行为和资源无泄漏。
* adapter 测试重点覆盖协议映射、错误翻译、命令执行编排，不重复 core 已覆盖的状态组合。
* 修改协议或生命周期语义时，测试与对应 spec 在同一变更中一起更新。

具体命令和测试分层见 [测试](/development/testing)。

## 相关文档

* blockx 仓库 `AGENTS.md`：仓库级原则、目录骨架、测试基线、commit / PR 规范。
* blockx 仓库 `docs/specs/code-style.md`：本页展开的来源，含 `api/` 边界、Sans-IO 与 Scoped Resource Context 的具体约束。
* blockx 仓库 `docs/specs/io-subsystem.md`：Scoped Resource Context 的完整定义。
* [仓库结构与代码分层](/architecture/repository-layout)
* [贡献流程](/development/contributing)
