Files
rond-with-you/.workbuddy/memory/MEMORY.md
T
tamakyi 3f1b737a78 记忆:把 .workbuddy/memory 纳入版本控制
用户要求这份记忆跟着仓库走,而不是只留在本机。

.gitignore 里挡路的是 `.workbuddy/`。改成反忽略六行,并且**只放行 .md**:
别用 `!.workbuddy/memory/**`——它会把 memory 下任何文件都放行,本项目真发生过
`web.test.exe` 混进暂存。复核用 `git check-ignore -q --no-index` 逐条打靶:
memory/*.png、memory/sub/x.md、memory/MEMORY.md.bak、plans/a.md 全 IGNORED,
17 个 .md 全 included;`git add -An .workbuddy/` 恰好 16 个文件。
其余 .workbuddy 内容(计划、本地设置、临时产物)继续忽略。记忆里没有真凭据
(唯一命中是 2026-09-14.md 里的 `password=rond-admin`,那是项目默认口令)。

顺带做两件整理:

1. 取证了注入上限。以前按「字节数」判断 MEMORY.md 是否超限是错的:
   WorkBuddy 的 `getWorkingMemoryContent()` 用 `MAX_MEMORY_CHARS = 1e4` 比 JS
   `content.length`,即**字符数**(中文 1 字算 1);超了 substring 截断、尾部静默丢失。
   本 details 与每日日志都不参与注入,只有 MEMORY.md 受约束。
   现已从 6604 字符收到上限的 66%,并把这条上限写进索引抬头。
2. 把「备份合并」「导出/恢复三条铁律」两段长文迁进 MEMORY-details.md 的卅一/卅二节,
   索引只留关键词指针。行尾统一为 LF(仓库 .gitattributes 是 `* text=auto eol=lf`)。
2026-09-30 18:31:08 +08:00

89 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rond-with-you 硬规矩(索引)
> 全文:`.workbuddy/memory/MEMORY-details.md`(34 节);验证机制:技能 `rond-fix-verify`;备份包解析/导出:技能 `rond-backup-parse`。本文件只做关键词索引——**新规矩写 details,别堆这里**。
>
> ⚠️ 本文件**按字符数**注入,硬上限 `MAX_MEMORY_CHARS = 1e4`(WorkBuddy `app.asar` 里 `getWorkingMemoryContent`;中文 1 字算 1,不是按字节)。超了会被 `substring(0,1e4)` 截断并写明「先整理再干活」——即整段尾部静默丢失。日常日志与本 details **不参与注入**,可以随便写。现值见 `wc -m`,留 20% 余量。
## 落库 / 精度(details 一、四、五、十六、十九、廿八A)
- 建地点+到访**同一事务**;新行号只走 `nextSrcPK`+`clearStaleRaw`,**禁 `MAX(src_pk)+1`**。`seq_test.go`
- 行程派生列同导入(`validateMovementForm`):`from/to_place_id`(留空=绕过区域过滤直露访客)、`distance_km`、`duration_min`。
- 表单精度低于真值 ⇒ 只改备注也**静默改时间/坐标**:`keepStoredTime`/`resolveMovementEnd`/`keepStoredCoord`;改到访时间须 `clampVisitMovements` 夹行程。
- `src_pk` 只单数据集内有效;跨备份键=`ZIDENTIFIER_`/`ZUID_`;`contentHash` 别用 `SrcPK|Arrival`。
- 坐标留档 `coord_source/coord_sys/src_lat/src_lon`;换算只走 geo 包与 `/api/coords`。
- 脏数据:`appleTime` 超范围浮点转 int64 **回绕非饱和**(先卡秒数);`validCoord` 非法归 `(0,0)`。`dirty_test.go`
- 聚合刷新(`saveVisitTags`/`refreshPlaceAgg`/`refreshDatasetAgg`/`activityFlags`)**必须返回 error**。`agg_error_test.go`
- 收藏切换走单语句 `UPDATE … RETURNING bookmarked`。`visit_bookmark_test.go`
## rond 导出(details 二、廿六)
- 铁律:**PG 覆盖管理列、`entity_raw` 还原其余列**;基线已有行保真机原值(`keepNilCols`+`known[pk]`)。
- 新行只补 `ZRAWLATITUDE/LONGITUDE`、`ZVISIT.ZIDENTIFIER_`/`ZACTIVITY.ZUID_`(确定性=表名+src_pk)、`ZICON_`、`ZLASTARRIVALDATE_` 哨兵 `-63114076800`。
- `ZMOVEMENT.ZTYPE_` 不能 NULL、**别当管理列**(会洗掉 type=4);`queryTransports` 必须 `GROUP BY transport_src`。
- rows 收尾必须 `drainRows`(先 `Err()` 再 `Close()`)。`parse_rows_test.go`
- **扫进裸 `bool` 的列必须先 `COALESCE`**:`weather.is_daylight` 漏写 ⇒ NULL ⇒ 整包拒收(details 卅C)。
- 核对导出包**别按留档 UUID 找行**:站点新建行会被补派生 UUID(`coreDataUUID`)。
## 迷雾 / 遮罩(details 三、九、十、廿二)
- `.fwss`=zip、条目 zlib;`Model/*/`=头 32768B+每块 515B;`Model/~/`、`Model/#/` 原样写回;清空/替换都要连 `fog_raw_files`。
- 导入三段式(preview 暂存 → 差异页现算 → `?stash=` 落地);带 stash=urlencoded、无 stash=multipart 两条都留。
- `fog_masks` 圆心 GCJ-02+半径,判定前换 WGS-84 网格;遮罩只对访客;`stats.Filter` 有**两套 where**(`visitsWhere`+`movementsWhere`),漏一个=「到访藏了里程还在」。
- 瓦片须与底图同 `crs`;带地图页须 `p.HasMap=true`。
- `tileDigits` 补前导 0;`tileName` 尾缀 `len(s)-2` 单数字编号时为 -1 ⇒ panic;解码守卫 `len(name)<8`。
- `fogIndexV` 必须 `atomic.Pointer`(普通指针=真 data race)。`fog_race_test.go`(-race)
## 前端 / 模板 / CSS(details 六~八、十七、廿三、廿八D)
- 改 Go 必须重启;模板/CSS/JS 只在 `debug=true` 热加载(`AssetV` 启动哈希)。
- 拼查询串走返回 `template.URL` 的方法(`PlaceLink`/`EditPlaceLink`/`MapLink`/`EditLink`),勿手工转义。
- 窄屏覆盖写 `app.css` **末尾那块**;宽表堆叠类名**成对** `class="tb tb-stack"`;触控 44px。`tb_stack_test.go`
- 加表格列:`PlaceCols`/`scanPlace` **成对**、新列追加最后;CJK 挤换行只抬该表 `min-width`。
- ⚠️ 模板 JS 别用「heredoc+python」写 `\n`(`<script>` 语法错、交互全哑而模板测试照过);用 Edit,改完 CDP 验 JS 报错为空。`inline_script_test.go`
- 只在访客复现的 bug:先比「无 cookie 原始 HTML vs CDP 后 DOM」;别让 JS 从 `{{if .IsAdmin}}` 块读 id。
- **异步回包一律发序号作废**:`var seq=++xxxSeq;` 在 `if(!on) return;` **之前**,回包首行 `if(seq!==xxxSeq) return;`。`map_async_cancel_test.go`
- `window.localStorage` 一律经 `lsGet`/`lsSet`(隐私模式裸访问会抛);`previewQueue[id]` 须在 `if (live)` 之前排队、回调消费后删。
## 编辑页 / 重复到访(details 三十)
- 「重复到访」卡片:判定与 `tools/dedup_site_visits.py` 同语义、**UUID 是前提**、基线对不上就不给按钮;改指合并标记只动**本来就有**的键(`create_if_missing=false`)。`dup_visits_test.go`
- **已并入 / 子段(`ZMERGEDTO`/`ZPARENT`)只在读侧收口**,判据唯一一份 `stats.SoloVisitSQL`;天气与位移**不收口**;导出侧一字节不动。收口点清单见 details 卅G。
- 相对计数断言会**假绿**(变异让基准值一起膨胀),必须写定义性不变量;`render` 是流式直写,`code==200` 不等于渲染完。
- ⚠️ ingest 地点聚合回填必须排在 `insertEntityRaw` **之后**(判据读 entity_raw)。
## 活动 / 类别(details 廿四、廿五)
- **活动是表,类别不是**——类别只是 `places.poi_category` 字符串列;显示名**绝不写进该列**(`TestCategoryLabelNeverTouchesValue`),走 `categoryLabel(v,custom)`,Go 侧算好塞。
- 站内新建行 `entity_raw.raw IS NULL`;`ZCATEGORY_` 真机只有 `MKPOICategoryXxx` 枚举。
- 类别候选:编辑/照片页=库里在用 `GROUP BY`;补全页=在用 ∪ 静态表;下拉留「其它(自己写)」,默认「不动」。
## 跨备份 / 附属数据(details 十九~廿一)
- 预演按 `VisitUUID`/`ActivityUUID` 对齐、只读;`raw #>> '{ZIDENTIFIER_,$blob}'` 是 base64 须转 hex 比;地点只认坐标(`PlaceCoordKey` 6 位)。
- 附属数据(备注/投票/图片)主键=`place_key` 坐标键(非 dataset+src_pk);Go 与 SQL 两套须逐位一致。
- ⚠️ `schema.sql` 迁移块必须收敛且排在依赖表之后(插在 `place_images` 前:开发库无症、**新库起不来**)。
- 合并 `backup_merge.go`:整个 `withTx`、不删除;新行 `*_visit_src` 写**库里的 src_pk**;必须 `writeRawRow`;删地点前先查有无到访(CASCADE)。
## 后端 / 性能(details 十、廿八C)
- `findSourceArchive` 先按 `size_bytes` 筛再算 sha256(`pickArchiveBySha`)。池 8/8 + `db.WarmUp(d,6)`;页面内查询 `runJobs`(≤6);别用 `fog.Index.All()`;改遮罩须 `masks.invalidate()`+`fogTiles.clear()`。
- `placeCols` 子查询必须带 `vN.dataset_id=p.dataset_id`。`stats/placecols_test.go`(按子查询分段判)
- multipart handler 直接 `ParseMultipartForm`(`r.ParseForm()` 毒化字段);**之前必须 `MaxBytesReader`**。五个上传口:备份/GPX/迷雾/照片/地点图片。
- 包了 `ResponseWriter` 必须实现 `Unwrap()`(`gzipWriter`/`headerSent`);`recover` 写错误页前须看 `headerSent`。`server_error_test.go`
- 「先落盘再解析」的上传 handler,落盘后每条失败路径都要删半成品。`upload_cleanup_test.go`
- 批量勾选走 `*Bulk` + `p.src_pk = ANY($3::bigint[])`(pgx 直接吃 `[]int64`);**全局内存缓存必须带上限**(`fillupCache` FIFO 淘汰最旧 1/4);**同一查询别在一个 handler 里查两遍**。`admin_notes_bulk_test.go`+`fillup_cache_test.go`+`stats_weekday_once_test.go`
- 加 `setting` 字段改三处:`Default()`/`Load()`/`settingsSubmit()`。
## 安全 / 跳转(details 廿八B)
- 跳转唯一判据 `safeInternalPath`(`view.go`);四处共用。`redirect_target_test.go`(含禁弱判据静态扫描)
- cookie `Secure` 按请求判断(`cookieSecure`),否则本机 http 与 CDP 都登不进。
- CSRF 只加同源 Origin/Referer 守卫(`csrf.go`):仅写方法、`Origin: null` 拒、两头都缺放行。
- `clientIP` 只认 XFF **最后一段**(须 `netip.ParseAddr`);⚠️ 投票匿名身份=`hash(cookie,IP)`⇒改取法即改身份。
- GPX 按属主收口:`/api/gpx` 取生效数据集 `user_id` 再 `WHERE user_id=$1`;`adminGpxDelete` 查 `RowsAffected()==0`。`gpx_owner_test.go`
- 下载名走 `downloadDisposition`(ASCII 兜底+`filename*=UTF-8''`);四出口都经此。`download_name_test.go`
- `writeRawRow` 必须上抛错误让 `withTx` 回滚。**两步验证两闸都要**:`loginGuard`(键 `"2fa:"+用户名`)+`CodeReplay.Consume`。`twofa_guard_test.go`+`code_replay_test.go`
## 口径 / 404 / 验收(details 十一~十三、廿七、廿八E、卅二)
- ⚠️ **跑全量验收前**:① `VACUUM (FULL, ANALYZE)`(死元组涨到 3.3GB 后级联 DELETE 变 IO 爬虫,看着像 hang);② 库内必须有 `is_active` 数据集,否则 `internal/stats` 6 用例**假 skip**。跑着时别并发跑定点测试。
- 区域判定**两端各过一遍再 AND**(同 `regionSQL`);`movementsWhere` 也必须两端各判,不能用 `EXISTS(... IN (from,to) ...)`;比较列一律 `COALESCE(col,'')=$n`。`region_consistency_test.go`
- 其余(累计停留口径、`Filter.Verdict` 下沉、404 分档、验收四件套、覆盖率 `-coverpkg`)见 details 卅二。
## 备份合并 / 导出链路(details 卅一 + 廿一)
- 对齐键:到访=`ZIDENTIFIER_`、地点=`PlaceCoordKey`(坐标 6 位,**不认名称**)、活动=`ZUID_`;合并前必须**重算差异**。
- 地点配对只有一份实现 `rond.MatchPlaces`(差异与合并共用);同名兜底必须有距离上限(`nameMatchMaxKm=1.0`)。
- 差异页默认 radio 是「**用实机**」;写合并测试要照这个提交(抓 `value="backup"`),否则测的不是真实点击行为。
- 自检不变量:合并后没有任何地点被搬动 >1km、也没有任何到访离它的实机地点 >1km。
- **三条铁律**:① app 只靠 `Z_PK` 解引用地点/活动 ⇒ 导出的编号必须与**数据集基线**同一套 ⇒ **基线必须是手机包**;② rond 导入是**整库替换** ⇒ 导入前先清空 app 数据;③ 站点数据是超集 ⇒ 用「手机导出作新数据集 + 旧站点导出当备份合并进去」对齐;`ZRAW` 这类从源包抄来的计数会越界,靠导出侧夹取兜住。