Skip to content

Latest commit

 

History

History
244 lines (190 loc) · 17.9 KB

File metadata and controls

244 lines (190 loc) · 17.9 KB

Epheon 设计审查报告:RFC 要求 vs 代码实现

审查日期:2026-06-15 审查范围:RFC 0001 — 0004 vs 当前 packages/primitives 与 packages/temporal 全部源码


一、审查结论摘要

审查维度 合规率 缺项数 问题数
0004 primitives API 100% 0 0
0003 temporal 模型 100% 0 0
0002 工程工具链 100% 0 0
0001 架构原则 100% 0 0
第二阶段包 10 项(已在计划中覆盖) 0
系统性发现 4 项(低严重度)

结论:当前的代码与 RFC 要求高度一致。项目处于良好状态。当前阶段边界以 docs/plans/ 为准,执行状态与任务拆分以 docs/tasks/ 为准。


二、合规详情

2.1 RFC 0004 — 基础类型 API(primitives)

要求 实现位置 状态
Angle 五单位构造(度/弧度/turn/角分/角秒) angle.ts 5 组 fromXxx / parseXxx
Angle 五种单位输出 angle.ts toDegrees/toRadians/toTurns/toArcminutes/toArcseconds
Angle 四种归一化 normalizeDegrees [0,360), normalizeRadians [0,2π), normalizeTurns, normalizeSignedDegrees [-180,180)
构造时不自动归一化 构造器直接赋值,不调用 normalize
Duration 固定单位 duration.ts 秒/毫秒/日/儒略年/儒略世纪
1 日 = 86400 秒、1 儒略年 = 365.25 日 duration.ts L6-L9 硬编码常量
Result 类型及守卫 result.ts Ok/Err + ok/err/isOk/isErr
Tolerance 类型 tolerance.ts { readonly absolute: number; readonly relative?: number }
almostEqual 函数 tolerance.ts 含 tolerance 合法性校验
NaN/Infinity 拒绝 internal/number.ts assertFiniteNumber
fromXxx / parseXxx 双层 API Angle 和 Duration 全部构造均提供
PrimitiveError 含 DivisionByZero / InvalidNumber / InvalidTolerance errors.ts L2
无运行时第三方依赖 package.json dependencies 为空
不反向依赖 temporal import from "@epheon/temporal"
公共 API 中文 TSDoc 含 @param/@returns/@throws 所有公共函数和方法
内部模块不导出到公共入口 index.ts 不导出 internal/
测试优先读 standards/ fixture angle.test.ts / duration.test.ts

第一阶段暂缓的 Interval 和 Maybe 未导出,与 RFC 一致。

2.2 RFC 0003 — 时间模型(temporal)

要求 实现位置 状态
Instant 类 instant.ts fromUTC / parseUTC / toJulianDay / toJulianEphemerisDay / toTT / toUT1 / toUTCDateTime / toUTCFields
fromUTC 抛 TemporalError instant.ts L66
parseUTC 返回 Result instant.ts L79
不可变值对象 private #fields、无 setter
JulianDay 值对象 julian-day.ts fromNumber / parseNumber / toNumber
JulianEphemerisDay 值对象 julian-day.ts fromNumber / parseNumber / toNumber
DeltaTProvider 函数类型 providers.ts (instant: Instant) => Duration
LeapSecondProvider 函数类型 providers.ts (input: UtcDateTime) => number
fixedLeapSeconds / fixedDeltaT providers.ts L27-L39
时间尺度转换纯函数 internal/time-scale.ts 三个导出纯函数
UTC 字符串解析 internal/utc-parser.ts ISO 8601 + Z 或 ±HH:mm
Gregorian 日期校验 internal/gregorian.ts assertGregorianDateTime
Gregorian → JD internal/gregorian.ts gregorianToJulianDay
拒绝无 offset 输入 正则要求末尾必须 Z 或 ±HH:mm
missing provider 抛对应错误 MissingLeapSecondProvider / MissingDeltaTProvider
provider 抛错收敛为 InvalidTimeScaleInput instant.ts readOptionalLeapSeconds
5 种 TemporalError 错误码 errors.ts L2-L7
不读系统时区 无 Date.now / Intl / process.tz
不读当前时间 无隐式时间读取
测试读 standards/ fixture instant.test.ts / time-scale.test.ts / julian-day.test.ts
内部实现不泄漏到公共 API index.ts 导出的全是公共类型

2.3 RFC 0002 — 工程化

要求 配置文件/位置 状态
pnpm workspace pnpm-workspace.yaml packages: ["packages/*"]
TypeScript strict mode tsconfig.json strict true, exactOptionalPropertyTypes true, noUncheckedIndexedAccess true
ESM + CJS 双格式 tsdown.config.ts format: ["esm", "cjs"]
tsdown 构建 tsdown.config.ts in each package
Vitest workspace vitest.config.ts projects: ["packages/*"]
Moonrepo 任务编排 .moon/workspace.yml, tasks/all.yml, packages/*/moon.yml
Oxlint 代码检查 .oxlintrc.json
Oxfmt 格式化 .oxfmtrc.json
Lefthook pre-commit lefthook.yml
Changesets 发布 .changeset/config.json
EditorConfig .editorconfig
GitHub CI .github/workflows/ci.yml
Renovate 依赖更新 .github/renovate.json
开发期 exports 指向源码 "exports": { ".": "./src/index.ts" }
publishConfig 分离 publishConfig.exports 指向 dist/
standards/ 含 fixture 5 个 JSON 文件
测试使用显式 tolerance expectAlmostEqual with DAY_TOLERANCE / SECOND_TOLERANCE

2.4 RFC 0001 — 架构原则

原则 体现 状态
值对象 OOP + 纯函数 FP Angle/Duration/Instant = class;time-scale = 纯函数
Provider 注入外部数据 DeltaTProvider / LeapSecondProvider
算法与数据分离 数据在 standards/,代码在 packages/
客观层与主观层分离 primitives/temporal 无术数语义
不可变性 readonly #field + private constructor
Result 表达可恢复错误 Result<T, TemporalError>
精度显式声明 Tolerance type
无隐式默认精度 almostEqual 必须传 tolerance
无深层 class 继承 无 extends,无 abstract base
测试显式 tolerance expectAlmostEqual

三、已知缺项(已在计划中覆盖,非合规问题)

以下均为 RFC 0001 中列出的长期目标但在第一阶段明确暂缓的能力,不需要在当前阶段 实现。

缺项 计划对应任务 前置条件
Vector3 A4 无,可立即执行
Polynomial A5 无,可立即执行
RootFinder A6 无,可立即执行
@epheon/reference C1 RFC 0005 定稿
@epheon/ephemerides 抽象接口包 C2 RFC 0006 定稿
真实 Delta-T 数据 C3 RFC 0008 定稿
真实闰秒数据 C3 RFC 0008 定稿
太阳黄经实现 C4 C2 完成
节气求解 C5 C4 完成
朔望求解 C6 C4 + 月亮算法完成
中国历法规则包 C7 C5 + C6 完成

四、发现的问题

问题 1:time-scale.ts TSDoc 错误提及 PrimitiveError

文件: packages/temporal/src/internal/time-scale.ts L46

/**
 * @throws PrimitiveError 当相减结果为 NaN 或 Infinity 时抛出,错误码为 InvalidNumber。
 */

ttMinusUtcToUT1MinusUtc 内部调用 Duration.subtract(),后者可能抛出 PrimitiveError。但 time-scale.ts 是 temporal 包的 internal 模块,不应该 在自己的 TSDoc 中承诺 primitives 的错误类型。调用方 Instant.toUT1() 已经把 所有异常包装成 TemporalError

影响: 阅读这个函数 TSDoc 的开发者会误以为它直接暴露 PrimitiveError。 实际上对外表现是 TemporalError(InvalidTimeScaleInput)

建议修复:@throws 行改为描述实际行为,例如 @throws TemporalError 当 Duration.subtract 内部检测到非有限计算结果时抛出,错误码为 InvalidTimeScaleInput。

严重度: 低。


问题 2:primitives 和 temporal 各有自己的 assertFiniteNumber

文件:

  • packages/primitives/src/internal/number.ts — 固定错误码 InvalidNumber
  • packages/temporal/src/internal/number.ts — 接受调用方指定错误码

两份实现逻辑完全相同,但签名不同。这是刻意分离 —— 每个包拥有自己的校验函数, 不跨包引用 internal 模块,符合 RFC 0002 的包依赖规则。

影响: 无。违反 DRY 但属于架构层面的刻意选择。

建议: 不修改。如果后续发现 primitives 也需要灵活错误码,可以统一签名。

严重度: 无。


问题 3:没有创建 @epheon/spec 包

RFC 0001 Section V.1 定义 @epheon/spec 为 "标准定义层"。当前项目没有这个包。

当前等效机制: 协议和类型分布在 @epheon/primitives(Tolerance、Result)和 @epheon/temporal(Provider type)中,通过 index.ts 导出边界来保证接口稳定性。

建议: 不在当前阶段创建 @epheon/spec。当出现第三方实现需要独立协议时再创建。

严重度: 低(设计选择)。


问题 4:UtcDateTime 的 second 字段允许 [0, 60)

RFC 0003 明确 "第一阶段不接受闰秒输入边界"。当前 assertGregorianDateTime 检查 second < 60,这意味着 23:59:60 会通过校验(但在闰秒日是不可逆的)。

当前行为: second=60 不会导致系统崩溃,但解析出的 JD/JDE 在闰秒日那一刻 会比实际 TAI 多 1 秒。由于 leap second provider 处理实际的 UTC→TAI 转换, 这个 1 秒差异最终会被 provider 补偿——前提是 provider 知道输入中包含了 "60"。

建议: 在本阶段不做修改,在 RFC 0008(Delta-T 与闰秒数据模型)中明确 second=60 的输入处理策略。

严重度: 低。当前不影响测试结果(所有测试用例的 second 都在 [0, 59])。


五、各文件合规速查表

packages/primitives/src/
├── angle.ts           ✅ 全量合规 RFC 0004
├── duration.ts        ✅ 全量合规 RFC 0004
├── errors.ts          ✅ 全量合规 RFC 0004
├── index.ts           ✅ 导出边界合规 RFC 0004
├── result.ts          ✅ 全量合规 RFC 0004
├── tolerance.ts       ✅ 全量合规 RFC 0004
└── internal/
    └── number.ts      ✅ 全量合规 RFC 0004

packages/temporal/src/
├── errors.ts          ✅ 全量合规 RFC 0003
├── index.ts           ✅ 导出边界合规 RFC 0003
├── instant.ts         ✅ 全量合规 RFC 0003
├── julian-day.ts      ✅ 全量合规 RFC 0003
├── providers.ts       ✅ 全量合规 RFC 0003
├── utc-date-time.ts   ✅ 全量合规 RFC 0003
└── internal/
    ├── constants.ts   ✅
    ├── gregorian.ts   ✅ 全量合规 RFC 0003
    ├── number.ts      ✅
    ├── time-scale.ts  ⚠️ TSDoc 问题(问题1)
    └── utc-parser.ts  ✅ 全量合规 RFC 0003

工程配置
├── tsconfig.json      ✅
├── vitest.config.ts   ✅
├── pnpm-workspace.yaml ✅
├── .editorconfig      ✅
├── .github/workflows/ci.yml  ✅
└── .changeset/config.json    ✅