109 lines
5.0 KiB
Markdown
109 lines
5.0 KiB
Markdown
# 业务规则(agent 自行落地的稳定约束)
|
||
|
||
不依赖 Electron、不依赖任何 daemon。下列规则从 `src/main/adapters/fansibote-fuzhong/adapter.ts` 提炼,agent 在做任何写操作前必须自己执行这些校验。
|
||
|
||
## 时间块归一
|
||
|
||
slot 的 `endDatetime` 形如 `HH:59:00`,代表该 slot 占据"开始时间所在整点"整块。
|
||
|
||
- `08:00-08:59` = 8 点这一小时块
|
||
- `19:00-19:59` = 19 点这一小时块
|
||
- 连续组合判定时把 slot 投影到 `[beginHour, endHour)`,按整点对齐
|
||
- `helper find-continuous` 内部已实现该归一,agent 不必手算
|
||
|
||
## 连续组合判定
|
||
|
||
判定"19-21 点完整连续组合"的步骤(agent 也可以直接调 `pospal.mjs find-continuous`):
|
||
|
||
1. 过滤出 `status in {available, limited}` 且无 enrollment 覆盖的 slots
|
||
2. 按 `txtClassroomUid` 分组
|
||
3. 在每个分组内按 `beginDatetime` 升序排序
|
||
4. 在每个分组内滑窗 `[targetStartHour, targetEndHour)`:
|
||
- 窗口第一个 slot 的 `beginHour == targetStartHour`
|
||
- 窗口最后一个 slot 的 `endHour == targetEndHour`
|
||
- 相邻 slot 整点连续(`prev.endHour == cur.beginHour`,无空缺)
|
||
- 全部同一 `txtClassroomUid`
|
||
- 全部不在 enrollment 覆盖区间
|
||
5. 任一不满足即视为无完整连续组合
|
||
|
||
**整段"19-21 点" ≠ "19-20 和 20-21 分别可订"**——必须是同一场地、连续、无活动、可订。
|
||
|
||
## 写操作前实时复核
|
||
|
||
锁场前必须**重新查询**一次目标日期的列表,不信任之前缓存的:
|
||
|
||
- 全部目标 slot 仍 `available` / `limited`
|
||
- 全部目标 slot 无 enrollment 覆盖
|
||
- 全部 slot 仍同一 `txtClassroomUid`
|
||
- 全部 slot 仍按目标时间整点连续
|
||
- 总价仍等于 `sum(cost)`,单个 cost 未变
|
||
- 日期未变
|
||
|
||
任一不符则放弃本次锁场,**不重试**。
|
||
|
||
## 错误分类与重试边界
|
||
|
||
| 现象 | 分类 | 处置 |
|
||
|---|---|---|
|
||
| HTTP 2xx + `successed: true` | success | 一次性操作,session 结束本次写 |
|
||
| HTTP 2xx + `successed: false` | rejected | 可换时段重试(同一会话同一时段最多 1 次) |
|
||
| HTTP 4xx | rejected | 同上 |
|
||
| HTTP 5xx | uncertain | **不重试**,告诉用户去小程序核实 |
|
||
| 网络错误 / 超时 | uncertain | 同上 |
|
||
| JSON 解析失败 | uncertain | 同上 |
|
||
|
||
**绝不在结果不确定时重试。** 已经发起的锁场,agent 必须假设服务端可能已经接受,必须提示用户去小程序核对。
|
||
|
||
## 写操作幂等与熔断
|
||
|
||
- 同一 session 中:
|
||
- 同一时段只允许一次"明确失败 → 换时段重试"
|
||
- 任何 uncertain 后立即停止本 session 的写操作,等用户去小程序确认
|
||
- 锁场成功后,session 内禁止再对该用户发起任何写操作;告诉用户去小程序支付
|
||
- 锁场成功后 `script.expireTime` 已过:锁场已失效,告知用户
|
||
|
||
## 脱敏与日志
|
||
|
||
- 日志中**禁止**出现:`PSPLVISITORID` 原值、完整请求头、完整响应、支付签名、Cookie、预约人姓名/手机号
|
||
- 访客凭证指纹:`pospal.mjs fingerprint <visitor-id>` → 12 字符 hex,仅用于日志关联
|
||
- 响应脱敏:`pospal.mjs redact < raw.json` → 移除 `script.paySign`、所有 `PSPL*` 头、任何 `customerName/customerTel`,输出可记录版本
|
||
- 待支付摘要只保存:`apptUid`、`venueAppointUids`、`expireTime`、金额、场地名、时间;**绝不**保存 `script` 对象
|
||
|
||
## 重复通知与去重
|
||
|
||
agent 自己实现轮询时的去重:
|
||
|
||
- 维护"已知完整连续组合"集合,key = `txtClassroomUid + 日期 + 起止整点 + 总价`
|
||
- 只对"本轮新出现"且"相对上一轮状态变化"的组合产生通知
|
||
- 持续可订不重复通知
|
||
- 消失后重现可以再次通知
|
||
- 同一组合的多次出现合并为一条通知
|
||
|
||
## 轮询节奏
|
||
|
||
- 默认每 10 秒一轮,由 agent 自行 sleep 实现
|
||
- 整点(如 `19:00:00.000`)到达时绕过冷却立即查一次,但仍要遵守"同一 date 的并发查询只允许一个在飞"
|
||
- 同一球场的写操作天然串行(锁场 / 释放 不并发);同一 session 不允许同时发起两笔锁场
|
||
|
||
## PSPLVISITORID 处理
|
||
|
||
- agent 在首次需要写操作时向用户索取,使用隐藏式输入:
|
||
- bash: `read -rs PSPAL_VISITOR_ID`
|
||
- Node: `readline` 关闭回显
|
||
- 任何等价手段
|
||
- 输入后只在 session 内存保存为 `process.env.POSPAL_VISITOR_ID`,绝不落盘
|
||
- session 结束或被新 session 替换时立即 `unset POSPAL_VISITOR_ID`
|
||
- helper 子命令只从 env 或 `--visitor` 参数读取;**优先用 env**避免值出现在进程列表
|
||
- 任何情况下都不得在日志、对话、git、临时文件中出现 `PSPLVISITORID` 原值
|
||
- 调试用指纹:`pospal.mjs fingerprint "$POSPAL_VISITOR_ID"` → 12 字符 hex
|
||
|
||
## 不允许做的事
|
||
|
||
- ❌ 用任何形式的"自动支付"或"自动点确认"
|
||
- ❌ 缓存 `script` 对象 / `paySign`
|
||
- ❌ 把 `PSPLVISITORID` 写入 Skill 文件、`.env`、配置、日志、对话
|
||
- ❌ 改写 `STOREID` / `userId` / `PROJECT_UID`(除非接口契约明确变更)
|
||
- ❌ 在结果不确定时重试
|
||
- ❌ 拆单(把"19-21"拆成"19-20"和"20-21"两次请求)
|
||
- ❌ 静默忽略字段缺失
|