Skip to content

✨ feat(timing): 实现 ListCalendarView 日历视图查询 - #171

Merged
jing-gou merged 11 commits into
1024XEngineer:mainfrom
jing-gou:dev/143-list-calendar-view
Aug 6, 2026
Merged

✨ feat(timing): 实现 ListCalendarView 日历视图查询#171
jing-gou merged 11 commits into
1024XEngineer:mainfrom
jing-gou:dev/143-list-calendar-view

Conversation

@jing-gou

@jing-gou jing-gou commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

结论

完成 Issue #143TimingTaskService::ListCalendarView 已按时间范围返回用户可见 occurrence,并合并已物化例外。本 PR 依赖开放 PR #161(Issue #141);由于 GitHub 不允许以个人 fork 分支作为 base,PR diff 会包含该依赖提交。请按“先看 #161,再看本 PR 最后一个提交 a14bd8b”的顺序评审。

背景

TimingTaskService 的日历查询仍由 kUnavailable stub 占位,调用方无法按范围读取一次性或周期日程,也无法观察 modified/completed/skipped 例外。

改动范围

  • 新增 TimingTaskStorePort::ListTasksListInstances 查询边界,并同步内存 fake。
  • 实现左闭右开范围、schedule/status 过滤、排序方向、page/page_size 分页和非法参数错误。
  • 对未物化的基础周期规则生成可查询 occurrence;已物化实例按 override 字段和状态覆盖基础 occurrence。
  • 新增一次性、周期展开、例外覆盖、过滤、分页、空结果和非法范围 Host 测试。
  • 明确没有改动:完整 RFC 5545/IANA tzdb、后台 Runner、生产 SQLite/NVS Adapter、触发投递、DTO/Profile/协议格式。

接口与依赖影响

新增两个 Store Port 纯虚查询方法;未改变公开 Service DTO、Profile Schema、持久化格式、协议或组件依赖方向。生产 Adapter 尚未进入本 Issue 范围。

测试与构建证据

  • ./scripts/run_pre_submit_checks.sh:通过。
  • C/C++:clang-format 18.1.8;Python:Ruff 0.12.7。
  • Host:14/14 测试通过;公共 API、架构、Profile 和 Python 检查通过。
  • IM Gateway:Node.js 24.19.0、pnpm 10.17.1,Prettier、ESLint、TypeScript、TSDoc 和测试通过。
  • 真实硬件、生产数据库和外部投递未覆盖,符合 parent [Timing] 完善定时任务模块核心业务能力 #137 边界。

已知风险

周期展开使用秒级 Gregorian/UTC 规则,不处理 IANA 时区和完整 RFC 5545 语义;大范围查询的生产数据库性能留给后续 Issue。

兼容窗口与回退

新增 Port 目前只有 Host 内存实现,未引入生产迁移。回退本 PR 即可恢复 ListCalendarView 的 stub 行为,无持久化迁移步骤。

Closes #143
Refs #137
Refs #161

为提醒规则管理提供原子批量写入,避免逐条保存造成部分更新。

服务校验 weak/strong snooze、日程归属和准点 strong 唯一性;内存 fake 与主机测试覆盖成功、冲突和写入失败路径。

Host、架构、Profile、Python 检查及 Node 24 Gateway CI 已通过;本机缺少 GNU GCC,gcov 覆盖率由 CI 确认。

Closes 1024XEngineer#141
服务层基于旧快照校验后写入时,多个并发请求可能绕过准点强提醒唯一性。

Store Port 现在要求在同一原子写入边界复核该不变量;内存 fake 和 Store 合同测试覆盖第二个 writer 被拒绝的场景。

./scripts/run_checks.sh 已通过。

Refs 1024XEngineer#141
实现左闭右开时间范围内的用户可见 occurrence 查询,支持日程和状态过滤、排序与分页。通过 Store Port 查询任务和已物化实例,并在不引入 RFC 5545/IANA tzdb 的前提下展开基础周期规则,使用 modified/completed/skipped 例外覆盖基础 occurrence。

RED:ListCalendarView 合法范围测试在原 kUnavailable stub 上失败。GREEN:补充最小查询 Port 和内存 fake 后通过一次性、周期展开、例外覆盖、过滤分页及非法输入测试。REFACTOR:将查询实现独立到日历翻译单元。

Refs 1024XEngineer#143

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two issues in the new calendar view implementation. Verification run: cmake -S tests/host -B build/host-tests && cmake --build build/host-tests && ctest --test-dir build/host-tests --output-on-failure passed.

return remainder < 0 ? quotient - 1 : quotient;
}

// Gregorian conversion is deliberately UTC-only; timezone database expansion is outside this issue.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TimingTask stores a time_zone, and registration preserves it, but calendar expansion derives weekdays/month days/year days using UTC civil dates only. For a non-UTC task such as Asia/Shanghai, a weekly Sunday 00:30 local occurrence is Saturday in UTC, so by_weekdays, monthly day matching, and yearly matching can include or skip the wrong local date. This should either expand in the task timezone or reject/normalize non-UTC tasks before using these calendar rules.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: 已按 #143/#137 的范围将该能力边界显式化:完整 IANA tzdb 不在本 PR 内;周期任务的日历展开现在仅接受 UTC,其他时区返回 kUnavailable,避免静默按 UTC 错算。已在 db5e3c8 和 Host 测试中覆盖。后续时区支持可单独拆 Issue。

return planned_times;
}

for (int64_t day = first_day; day <= last_day_start && day <= std::numeric_limits<int64_t>::max() - kSecondsPerDay;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ListCalendarView accepts any valid [range_start, range_end) and then scans one day at a time for every active task before pagination. A large but valid request, for example a multi-century or accidental INT64_MAX range, can tie up the service despite a small page_size. Please cap the query window or seek directly to the first candidate occurrence per recurrence type and stop after enough rows for the requested page.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 该风险属于 parent #137 明确排除的生产数据库性能优化范围。本 PR 已在正文记录逐日展开和大范围查询风险;本次不引入查询窗口上限,以免改变 #143 的有效左闭右开范围语义。大范围/高性能展开应在后续带有真实 Adapter 约束的 Issue 中单独设计。

…ar-view

# Conflicts:
#	tests/host/timing_task_service_test.cc
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.85714% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...icelife_timing/src/timing_task_service_calendar.cc 82.85% 7 Missing and 17 partials ⚠️

📢 Thoughts on this report? Let us know!

补充周、月、年周期展开,Store 查询失败、分页参数和过滤分支的公开 Service 测试,满足 C++ patch 覆盖率门禁。

Refs 1024XEngineer#143
周期规则使用 UTC 民用日期展开;对其他时区返回明确的不可用错误,避免静默产生错误的星期、月日和年月日匹配。补充 Host 失败路径测试。\n\nRefs 1024XEngineer#143
@jing-gou

jing-gou commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@fennoai 你是资深后端/全栈架构师 + 嵌入式硬件工程师复合型专家,执行严格、客观、不留情面的代码仓库Review,请遵循下面所有评审规则,逐条输出审查结果,禁止敷衍、禁止只说空话、禁止笼统概括。
硬性约束:最终输出评审条目数量不少于20条,若直观可见问题不足,主动挖掘隐性架构隐患、软硬件兼容风险、长期运行潜在缺陷补足条目,不得简化评审内容。

评审维度

  1. 架构与模块设计
  • 模块职责是否清晰,是否存在循环依赖、职责混杂
  • 分层是否合理,是否违反单一职责原则、开闭原则
  • 接口抽象、依赖注入设计是否规范,有无硬编码耦合
  • 软件与硬件模块边界是否清晰,硬件相关逻辑是否侵入业务层
  1. 代码规范与可读性
  • 命名:变量、函数、类、文件命名是否语义清晰,禁止模糊命名、拼音命名
  • 注释:复杂逻辑、硬件时序、特殊寄存器配置必须注释;冗余注释、无效注释、过期注释需要指出
  • 代码格式、风格是否统一,是否存在大量魔法数字、魔法字符串,硬件参数无常量定义
  1. 性能隐患
  • 循环内IO、数据库重复查询、不必要的内存占用、低效算法
  • 资源是否释放(连接、句柄、定时器、文件流、硬件外设句柄)
  • 嵌入式场景:阻塞轮询、中断处理耗时过长、内存频繁分配释放
  1. 安全性检查【重点】
  • 输入校验、SQL注入、XSS、权限控制、敏感信息明文打印
  • 密钥、token、数据库地址、硬件访问口令是否硬编码提交到仓库
  • 外部指令下发至硬件驱动缺少权限校验,存在设备失控风险
  1. 健壮性 & 异常处理
  • 是否缺少异常捕获、错误分支处理
  • 参数判空、边界条件、失败重试逻辑是否完备
  • 硬件通讯异常(I2C/SPI/UART断线、设备无应答)缺少容错、恢复逻辑
  • 缺少硬件故障状态上报、故障隔离机制
  1. 硬件驱动 & 软硬件协同评审(新增专项)
  • 驱动代码与硬件原理图引脚定义是否匹配,无硬件版本兼容逻辑
  • 外设操作缺少电平保护、超时判断,存在烧毁外设芯片风险
  • 运动控制逻辑(如有)缺少软限位、急停、碰撞检测保护
  • 上下位机通讯协议:缺少校验和、重传、断线重连机制
  • 硬件参数(电流、电压、速度阈值)直接写死代码,无统一配置入口
  1. 测试覆盖
  • 是否缺少单元测试、边界用例
  • 核心业务逻辑、硬件驱动逻辑无测试直接合并风险
  • 缺少硬件异常工况、极限压力场景测试用例
  1. 工程规范
  • 无用代码、注释掉的废弃代码是否大量遗留
  • 依赖包版本风险、冗余依赖、存在已知漏洞依赖
  • 配置区分环境(开发/样机/生产),环境配置是否混写
  • 固件、上位机、3D模型、原理图版本号不统一,无关联管理
  1. 可维护性 & 扩展性
  • 新增需求是否需要大面积修改原有代码
  • 是否存在大量重复代码,缺少公共封装
  • 硬件型号迭代时,软件需要大规模改动,缺少适配抽象层

输出要求

  1. 问题分级:🔴严重阻塞(必须修改才能合并/上机测试) / 🟡建议优化(不阻塞但强烈建议重构) / 🟢可选改进
  2. 每条问题固定格式:【文件路径+代码位置】问题描述 + 风险说明 + 可直接落地的修改方案
  3. 条目数量强制≥20条;问题较少时区分表层问题与隐性长期风险,拆分为独立条目逐条列出,禁止多条问题合并为一条
  4. 最后汇总三部分:
    ① 整体仓库风险总结
    ② 优先级整改清单(阻断项优先)
    ③ 长期架构、软硬件协同优化方案
  5. 如果存在架构层面缺陷,单独提炼顶层设计问题,不要只局限单行代码
  6. 不要美化结论,发现隐患直接指出,客观评估风险等级;不使用“建议考虑”这类温和模糊表述,明确写出故障后果(程序崩溃、硬件烧毁、机构失控、数据泄露等)

现在开始对提供的仓库代码/PR变更、硬件资料、结构模型进行评审

@fennoai

fennoai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review 结论

本次按固定范围 06484ca...db5e3c8 审查 PR #171。聚焦变更文件后,结论是不应直接合并到可长期运行/生产适配路径;当前 Host 测试通过,但实现把全量扫描、UTC 限制、异常合并和新 Store Port 合同问题留在了核心查询路径,后续接真实 SQLite/NVS 或外部提醒投递时会放大为性能、数据隔离和行为错误。

已验证:./scripts/run_host_tests.sh -R 'timing_task_(service|store)_test' 通过;git diff --check 通过;变更 diff 未发现 token/secret/password/API key 字样。仓库内未发现硬件原理图、PCB、3D 结构模型类文件,本 PR 也未改硬件驱动/引脚/外设代码,因此无法执行引脚、电平、机构限位类实物一致性评审。

顶层设计问题

  1. 🔴严重阻塞:【components/voicelife_timing/include/voicelife/timing/timing_task_store.h:35-40】ListTasks() 只定义“查询所有可见定时任务”,没有用户/租户/范围/schedule 过滤入参。风险:生产 Store 一旦保存多用户或多日程数据,业务层必须全表拉取再过滤,造成数据越权暴露和 OOM/看门狗复位。修改方案:把 Port 改为 ListTasks(CalendarTaskQuery{owner_id/schedule_id/range_start/range_end/status}),由适配器在持久化层完成权限与范围过滤。
  2. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_calendar.cc:151,167】日历查询先 ListTasks() 再对每个任务 ListInstances(task.id),形成全表扫描 + N+1 查询。风险:任务量上来后一次日历请求会拖死 SQLite/NVS,嵌入式设备主循环阻塞导致提醒投递延迟或系统复位。修改方案:新增批量查询端口 ListCalendarTaskRows(query) / ListInstancesForTasks(task_ids, range),让数据库一次性按索引返回候选集合。
  3. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_calendar.cc:163-165】只要查询范围内存在任意 active 非 UTC 周期任务,未指定 schedule_id 的全局日历查询会整体返回 kUnavailable。风险:一个 Asia/Shanghai 周期任务会让其它 UTC/一次性日程也完全不可见,用户日历页面直接失败。修改方案:先用可证明的任务时间窗口过滤候选;非 UTC 任务改为单条 occurrence 能力降级/跳过并返回 partial warning,或接入统一时区展开器后再启用。
  4. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_calendar.cc:80-102,146-149】range_end - range_startpage_size 没有上限,ExpandTask 按天循环。风险:调用方传十年/百年范围即可占满 CPU 和内存,嵌入式设备会卡死或被看门狗重启。修改方案:服务层强制最大查询窗口、最大 page_size、最大展开 occurrence 数,超过限制返回 kInvalidArgument
  5. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_calendar.cc:82-86】FloorDiv(...)*kSecondsPerDay 在接近 int64_t 极值时可能有符号溢出,属于未定义行为。风险:恶意或异常输入可触发错误展开、死循环甚至崩溃。修改方案:把允许时间戳限制在业务窗口(例如 2000-01-01 到 2100-01-01),或使用 checked arithmetic 并在溢出时返回 kInvalidArgument
  6. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:14,92-97】channelsource 只判空,允许任意字符串进入提醒规则。风险:后续外部投递适配器若按 channel 路由,会出现未知通道触发、日志注入或越权硬件/消息通道调用。修改方案:改为枚举或注册表白名单,只允许 voice 等已注册通道;source 限定为系统定义枚举或受控常量。
  7. 🔴严重阻塞:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:14,92-95】offset_minutesmax_snooze_countsnooze_interval_minutes 没有上界。风险:极大负偏移在未来派生 planned_trigger_at 时会下溢,极大 snooze 参数会产生异常长定时器或触发风暴。修改方案:定义领域上限,例如提前不超过 30 天、snooze 次数不超过 10、间隔不超过 24 小时,并在输入校验中拒绝越界值。
  8. 🔴严重阻塞:【components/voicelife_timing/CMakeLists.txt:1-10, components/voicelife_timing/src/timing_task_service_calendar.cc:185】组件使用 C++20 的 unordered_set::contains,但 ESP-IDF 组件 CMake 未显式声明 C++20 标准;Host CMake 设置了 C++20,固件构建不一定一致。风险:主机测试通过但固件 Profile 编译失败。修改方案:在固件构建链路显式设置 C++20,或把 .contains() 改为 C++17 兼容的 find(...) != end()

逐项问题清单

  1. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:158-160】只检查 task.status,没有检查 task.deleted_at。风险:软删除但状态仍为 active 的任务会重新出现在日历中,用户看到已删除日程。修改方案:过滤条件加入 task.deleted_at == 0,并补软删除任务不可见测试。
  2. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:174-177】deleted_at != 0 的实例被直接跳过且不写入 materialized_planned_at。风险:如果删除实例用于表示“取消单次 occurrence”,基础周期 occurrence 会被重新生成,取消无效。修改方案:明确 deleted instance 语义;若其表示墓碑,应先记录 planned_at 抑制基础 occurrence,再决定是否展示。
  3. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:98-99,172-181】effective_until 只限制未物化基础 occurrence,不限制已物化实例。风险:未来范围被截断后,已有实例仍可能泄露到日历,取消/终止边界不一致。修改方案:对 instance 也应用 task 生命周期窗口,或在 Store 查询中只返回有效窗口内实例。
  4. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:124-132】未物化 occurrence 的 planned_end_at 被设成 planned_at。风险:日历 UI 会得到零时长事件,可能无法渲染或与提醒触发时间混淆。修改方案:在 TimingTask 或查询投影中引入 duration/end_at;没有结束时间时显式返回默认持续时间或 nullable end 字段。
  5. 🟡建议优化:【components/voicelife_timing/include/voicelife/timing/timing_task_contracts.h:141, components/voicelife_timing/src/timing_task_service_calendar.cc:109-121】CalendarOccurrence::title 从未填充。风险:接口名是“用户可见安排”,但结果没有标题,调用方只能显示空日程。修改方案:日历查询需要 join/投影 Schedule 标题,或从合同中删除 title 并让上层明确二次查询。
  6. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:195-203】按 kActualTriggerAt 排序时,未触发 occurrence 的 actual_trigger_at=0 会排在最前。风险:用户按实际触发时间查看时,未触发任务压过已触发历史,排序语义错误。修改方案:把 actual trigger 改为 optional;排序时 pending/null 统一置后,或只允许 triggered/completed 状态使用该排序。
  7. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:207】occurrences.size() 被强转为 int。风险:极端展开结果超过 INT_MAX 后 total 溢出为负数,分页计算失真。修改方案:在展开上限处截断并返回错误,或把 CalendarView::total 改为 int64_t/size_t
  8. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:28-38】Gregorian 转换把年份转成 int,但没有限制输入 timestamp。风险:极端时间戳导致 year 窄化溢出,月/年规则展开错误。修改方案:在入口校验业务支持年份范围,并为边界年份补测试。
  9. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_calendar.cc:163】时区能力用字符串硬编码 UTC,且只接受精确值。风险:Etc/UTCutc 或后续平台标准化值会被错误拒绝,跨平台数据迁移失败。修改方案:引入 TimeZoneId 规范化函数,注册和查询都使用同一标准形式。
  10. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:103-114】准点强提醒唯一性统计没有排除 deleted_at != 0。风险:软删除的旧强提醒仍阻止新准点强提醒创建,用户无法恢复提醒。修改方案:统计条件加入 rule.deleted_at == 0,并在 Store 原子约束中使用同样谓词。
  11. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:106-111】result_rules 返回所有规则,没有过滤 deleted/disabled,也没有在接口注释说明。风险:调用方把历史规则当当前可用规则渲染或投递,产生重复提醒。修改方案:合同明确返回 active-only 还是 all-with-status;若是当前规则列表,应过滤 deleted_at != 0 并按 status 分类测试。
  12. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:76-80】ID 生成冲突直接失败,没有重试。风险:短暂 ID 碰撞会让合法请求失败,批量规则创建更容易触发偶发故障。修改方案:为新规则 ID 生成做有限次数重试;耗尽后再返回 kConflict
  13. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:62-100】command.rules 数量没有上限。风险:单个请求可分配大量 unordered_map/vector 内存,嵌入式场景会直接耗尽堆。修改方案:定义每任务最大提醒规则数和单次请求最大规则数,超过上限返回 kInvalidArgument
  14. 🟡建议优化:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:84-90,92-100】允许更新任意已存在规则字段,但不校验 deleted_at 或 disabled 状态是否允许修改。风险:删除/禁用后的规则被悄悄改写,审计和幂等语义混乱。修改方案:对 existing->second.deleted_at != 0 返回 kNotFound,对 disabled 规则返回 kConflict 或提供显式恢复命令。
  15. 🟡建议优化:【components/voicelife_timing/include/voicelife/timing/timing_task_store.h:52-60】ListInstancesUpsertRules 的 Port 注释没有规定是否返回/写入软删除记录、排序、原子隔离级别。风险:Fake、SQLite、NVS 适配器各自理解不同,同一 Service 在不同存储上行为不一致。修改方案:在公开 Port 注释中写明 deleted/status 过滤、排序稳定性、事务隔离和冲突返回码。
  16. 🟡建议优化:【tests/host/support/timing_fakes.h:113-115】AddTask/AddInstance 直接绕过 Store 校验。风险:测试可构造生产不可能出现的状态,掩盖服务层和适配器边界错误。修改方案:把测试夹具改为显式 builder,并复用生产校验或至少检查 id/task_id/request_id/schedule_id 基本约束。
  17. 🟡建议优化:【tests/host/timing_task_service_test.cc:311,329】月规则测试只断言 total >= 2 / >= 3。风险:多生成、重复生成、跨月错误都能通过,周期算法回归不会暴露。修改方案:固定时间范围并断言每个 expected timestamp 的精确集合。
  18. 🟡建议优化:【tests/host/timing_task_service_test.cc:142-366】日历测试缺少降序分页、第二页、kActualTriggerAt、deleted task、deleted instance、effective_until、跨多个 task 的非 UTC 混合查询。风险:当前主要验证 happy path,无法保护真实日历列表行为。修改方案:把这些场景拆成独立 Host 测试,每个测试只断言一个语义。
  19. 🟢可选改进:【components/voicelife_timing/src/timing_task_service_calendar.cc:41-43,63-74】Contains 对 by_xxx vector 做线性查找。风险:目前数组很小,风险低;未来若开放复杂规则,重复日级循环会放大 CPU。修改方案:注册时规范化并排序/去重 by_xxx,查询时使用小型 bitset 或预计算集合。
  20. 🟢可选改进:【components/voicelife_timing/src/timing_task_service_calendar.cc:21-38】UTC-only Gregorian 算法只有一句英文注释,没有说明闰年、闰秒、DST、IANA tzdb 的边界。风险:后续维护者容易把它误当完整 RFC 5545 展开器,导致周期日程错误。修改方案:补中文实现注释并在接口文档写明“秒级 UTC Gregorian 子集”。
  21. 🟢可选改进:【components/voicelife_timing/src/timing_task_service_reminder_rules.cc:13-23】提醒规则校验逻辑直接散在 service 源文件。风险:未来 Register/Update/Delete/Trigger 派生都要复用同一规则时会复制分叉。修改方案:抽到 timing_task 领域策略或 ReminderRulePolicy,由 service 调用统一校验。
  22. 🟢可选改进:【tests/host/timing_task_service_test.cc:119-761】单个 main() 累积大量场景,失败定位依赖中文字符串。风险:新增场景后测试文件继续膨胀,维护成本高。修改方案:拆成 RunCalendarViewTests()RunReminderRuleTests()RunRegistrationTests(),每组独立准备夹具。

整体仓库风险总结

核心风险集中在 Timing 领域层和 Store Port 边界:当前实现适合作为 Host fake 上的功能切片,但还不是可直接承载真实持久化和长期运行设备的查询设计。安全上未发现密钥提交,但缺少用户/范围过滤入口和提醒通道白名单;性能上全量扫描、N+1 查询、无范围上限会在嵌入式设备上造成阻塞;硬件专项未发现直接烧毁外设或机构失控风险,因为本 PR 未触碰硬件驱动、引脚、电气参数或结构模型。

优先级整改清单

  1. 先修阻断项:Store 查询入参必须带权限/范围过滤,替换全量 ListTasks() + N+1 ListInstances(),给 range/page_size/规则数量/提醒参数加硬上限。
  2. 修正非 UTC 周期任务导致整页失败的问题;不能让一个不支持展开的任务拖垮整个日历查询。
  3. 统一软删除语义:task、instance、reminder rule 的 deleted_at 在 Service、Store、测试 fake 中必须一致。
  4. 明确固件 C++ 标准或移除 C++20 API 依赖,避免 Host 过了但 ESP-IDF 编译失败。
  5. 补齐精确断言测试:分页、排序、actual trigger、deleted/effective_until、极端范围、非 UTC 混合任务。

长期架构与软硬件协同优化方案

  • 建立 CalendarExpansionPolicy/RecurrenceExpander 领域组件,Service 只负责鉴权、端口调用、分页组装,避免把时间算法、查询策略和 DTO 组装堆在一个方法里。
  • Store Port 改为查询模型驱动,持久化层必须拥有索引、范围过滤、软删除过滤和批量实例查询能力;Fake 必须模拟这些约束,而不是只保存 unordered_map。
  • 提醒通道建立 Adapter 注册表和权限模型,业务层只产生受控 intent,任何外部 IM/语音/硬件投递都经过白名单、超时、重试、故障状态上报。
  • 后续涉及硬件触发、音频外设、运动机构或电源控制时,PR 必须带硬件版本、引脚映射、超时/重试策略、故障隔离和真机验证证据;不能用 Host mock 代替上机风险验证。

@jing-gou

jing-gou commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Reference: 对 2026-08-06 汇总 Review 的逐项范围判断

评审范围应以 Issue 143 为准,并受 parent Issue 137 约束:本切片只实现 ListCalendarView 的范围、过滤、排序、分页、基础周期展开和已物化例外合并;完整 recurrence/IANA tzdb、Runner、SQLite/NVS Adapter 与生产数据库性能优化均明确范围外。PR 161 的依赖提交不改变该边界,提醒规则项应在 PR 161 评审。

本 PR 已处理

  1. 评审项 5(适用,已修复于 b3d35ccINT64_MIN 附近的 FloorDiv(...)*86400 可能不可表示。入口现在拒绝不能安全对齐日边界的下界,并有回归测试;没有随意收窄普通有效范围。
  2. 评审项 9(适用,已修复于 b3d35ccListTasks() 的契约要求 Service 负责生命周期过滤,因此 active 但 deleted_at != 0 的任务必须不可见。实现已过滤并测试。
  3. 评审项 23(部分适用,已修复于 b3d35ccListInstances() 现在明确返回软删除记录,由日历 Service 决定用户可见性。UpsertRules() 的原子写入/冲突语义原本已在 Port 注释中定义;排序不构成 Adapter 合同,因为 Service 最终排序。
  4. 评审项 25(适用,已修复于 b3d35cc:月规则测试从下界断言收紧为精确 3/4 条 occurrence。
  5. 评审项 26(部分适用,已修复于 b3d35cc:补充降序、第二页、kActualTriggerAt 和软删除 task 的公开 Service 测试,覆盖 Issue 143 已承诺的排序方向、分页与可见性。

适用的长期风险,但不属于 Issue 143 的可安全修复范围

  1. 评审项 1:用户/租户不在 TimingTaskCalendarViewQuery 或该组件授权模型中;新增 owner-aware Port 会改变公开模型。属于跨模块鉴权/生产持久化设计,不是本切片。
  2. 评审项 2:全表/N+1 的 SQLite 索引与批量查询模型属于 Issue 143/137 明确排除的生产数据库性能优化;当前只有 Host fake,不能凭空指定生产查询模型。
  3. 评审项 3:完整时区展开明确范围外。当前 kUnavailable 是不产生错误 UTC 日期的明确能力边界;“partial warning/跳过”需要新 DTO 语义且会静默遗漏 occurrence,不能在本 PR 擅自引入。
  4. 评审项 4:大窗口和容量上限需要产品定义最大范围、page_size 和 total 语义;Issue 143 只规定左闭右开有效范围。其算术安全子问题已按评审项 5 修复,扫描策略留给带生产 Adapter 约束的后续 Issue。
  5. 评审项 10:取消单次 occurrence 的领域状态是 kSkippeddeleted_at 目前表示软删除记录而非“取消墓碑”。在 Update/Cancel 语义未落地前,把 deleted instance 当抑制标记会擅自定义数据含义。
  6. 评审项 11effective_until 与已经物化的例外如何交互属于尚未实现的 Update/Cancel 切片;本 PR 仅对基础生成规则应用已存在的字段,不能猜测未来变更语义。
  7. 评审项 15、16CalendarView::total 为既有 int 合同,极端数十亿年查询与上限策略同属评审项 4 的容量设计;不在无迁移的日历查询切片改变公开 DTO。
  8. 评审项 17:接受 Etc/UTC 或大小写归一化需要 TimeZoneId 规范,属于 IANA tzdb/时区策略工作,明确范围外。
  9. 评审项 27:规则字段受现有值域校验且容量很小;将 vector 换 bitset 是复杂 recurrence/性能优化,不是验收行为。
  10. 评审项 30:测试文件是既有多接口 Host 聚合;拆分只改变组织,不改变 Issue 143 行为。CI 将其作为既有文件规模 warning 而非失败,本 PR 不混入机械重构。

不适用或已有合同定义

  1. 评审项 8:不成立。unordered_set::contains 已在本 PR 的 ESP-IDF 6.0 / ESP32-S3 CI 构建中实际编译通过,Host 与固件不一致的推断没有证据。
  2. 评审项 12TimingTask 没有 duration/end_at;未物化 occurrence 是时间点,planned_end_at == planned_at 是当前 DTO 的无时长表示。引入 duration 或 nullable 字段会扩大公开合同。
  3. 评审项 13:标题归 Schedule 领域所有,Timing Store 无标题来源。该查询返回 schedule/task ID 供上层投影;跨组件 join/新 Port 不在 Issue 143。
  4. 评审项 14actual_trigger_at 是现有非 optional 的数值字段,未触发值为 0;按该字段排序的数值语义稳定。替换为 null-last 会改变合同,已新增实际触发降序测试保护当前语义。
  5. 评审项 24AddTask/AddInstance 是测试夹具,用于构造生命周期边界状态;生产注册路径仍由 Store 校验。复用生产写入会让 Service 测试无法表达软删除等投影输入。
  6. 评审项 28:实现和 PR 都已明确“UTC-only,非 UTC 返回 kUnavailable,完整 tzdb 范围外”。增加实现注释不能替代也不需要重开时区设计。

不属于本 PR:PR 161 / Issue 141 的提醒规则项

  1. 评审项 6、7、18、19、20、21、22、29:这些均定位在 timing_task_service_reminder_rules.ccUpsertRules。它们属于 PR 161 / Issue 141;其中 PR 161 已明确数值上限未定义且不含实际投递。把 channel 白名单、最大 snooze/offset、批量容量、删除/禁用更新语义、ID 重试和 policy 抽取纳入 Issue 143,会违反“一条公开 Service 行为”的切片边界。

结论:Issue 143 的阻断性问题已按最小可验证方式修复;其余适用风险已留在正确的后续设计边界,而不以未定义的生产策略、时区语义或依赖 PR 行为阻断本切片。./scripts/run_pre_submit_checks.shb3d35cc 已通过。

软删除任务不再返回到用户日历;拒绝无法安全对齐日边界的 INT64_MIN 邻近范围,避免 FloorDiv 后的乘法溢出。\n\n明确 ListInstances 需提供软删除记录供 Service 决定可见性,并补齐降序、第二页、实际触发时间和精确月规则覆盖。\n\nRefs 1024XEngineer#143
@jing-gou
jing-gou force-pushed the dev/143-list-calendar-view branch from 132a935 to b3d35cc Compare August 6, 2026 08:19
@jing-gou

jing-gou commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

补充:reference comment 中的 132a935 已因显式加入 的可移植性修复 amend 为 b3d35cc;该提交已重新运行本地完整门禁。

同步 upstream/main 的定时任务更新、提醒规则删除、语音和 IM Gateway 能力,并在冲突处保留 Issue 143 的 ListCalendarView 实现。补齐 ListTasks Port、日历构建目标与 fake 行为,明确软删除实例由 Service 过滤。

已通过 clang-format 18、Ruff 0.12.7、主机 30 项测试、架构与固件配置检查,以及 Node.js 24 下 IM Gateway 145 项测试;PostgreSQL 契约按门禁规则因本机未启动跳过。

Refs 1024XEngineer#143
upstream/main 引入的 vendored cJSON 不属于 VoiceLife 业务源码,也不应成为 C++ patch 覆盖率门槛的一部分。将 third_party 目录加入 Codecov ignore,保留业务组件覆盖率要求。

已验证 codecov.yml YAML 语法和 git diff 检查。
third_party 排除规则未影响 C++ patch 覆盖率失败,根因是日历查询分支覆盖不足。恢复原有 Codecov 范围,避免无效地改变全局统计口径。
覆盖周期规则筛选、effective_until、软删除实例、非法分页与越界分页,验证 ListCalendarView 在 Issue 143 边界内的展开、过滤和分页行为。

Refs 1024XEngineer#143
@jing-gou
jing-gou merged commit 1819932 into 1024XEngineer:main Aug 6, 2026
13 checks passed
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.

[Timing] 实现 ListCalendarView 日历视图查询

2 participants