Skip to content

host: ask the temp name before a meeting record write lands through it #2865

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 1d801ddfde1547c98183be39c4bf921f
Branch: dev/3-temp-name-meetings
Base: 2eb9661
At: 2026-11-11T10:50:35Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead
Bot: FlyBot Dev-3
Risk tier: T2

现象

#2110 给五条「写临时名再 rename」的写入点加了守卫:临时名上出现链接、目录或检查失败时,在写第一个字节之前就拒绝,并把临时路径写进回答。live main 上接了这套守卫的写入点现在有 12 个。

core/src/host/meetings.js 这条写入点没有接。 它直接 writeFile/writeFileSync 落盘,没有「先问临时名」的检查:别人预先在 <file>.<pid>.tmp 放一个符号链接,会议记录的字节就穿过链接落到别处,再把那个名字 rename 成正式文件——和 #2097/#2109 同一条路径。

会议面多一层:#1781 已经要求「会议状态先落盘再回答」,所以这条写入点是回答前的必经之路,它被穿过的时候失败是不可见的——工具照样回 decided。

复现(live main,node 探针)

  1. 取一个 home,在会议记录文件的临时名位置 ln -s <外部文件> <meeting 文件>.<pid>.tmp;
  2. meeting 工具跑 open → position → decide(core/test/meetings-persist.test.js 的 setup 就能复用);
  3. 今天的结果:外部文件被写入会议记录的字节,临时名被 rename 成正式文件,工具回答成功,没有任何拒绝或警告。

期望行为

  • 临时名上是符号链接、目录、设备,或检查跑不起来 ⇒ 写第一个字节前拒绝,回答带出临时路径,不写、不 rename;
  • 目标叶子不查(#2110 已论证 rename(2) 不跟随最后组件);
  • 正常路径一字不变:core/test/meetings.test.js、meetings-persist.test.js、meetings-decide-paths.test.js、meetings-unknown-level.test.js 全绿。

修法

照 #2110 的 tempWritable()(core/src/host/real-dir.js);拒绝时的返回面沿用 meetings 现有的风格(工具回答里的错误句,别新造错误类型)。

红先要求(重点,不接受只测 happy path)

  • 临时名是符号链接 ⇒ 期望拒绝,且外部文件零字节变化、正式文件没被替换;
  • 临时名是目录 ⇒ 期望拒绝;
  • 一条正向:临时名不存在 ⇒ 正常落盘、重启后 read 还能读到(P-209 那条语义不能退)。

旧断言两遍全绿、新断言两遍红并打印实际 vs 期望,复原源码后再两遍全绿。

范围

  • core/src/host/meetings.js(临时名守卫);
  • 测试按现有风格(core/test/meetings-persist.test.js 或新文件);
  • patches/ds-bot/ 由 generate 写回,不手改 mirror;
  • arch-docs 判出来的 docs 面 + 一个 changelog fragment。

不做:目标叶子检查、nlink > 1 判定。

查重

  • 搜索 tempWritable:23 条全部 closed,无 open 同题;
  • 搜索 meetings write guard:29 条,最近的是 closed 的 #2592/#2593(手改 level 的容错)、#1781(先落盘再回答),与「临时名守卫」不同题;
  • live main grep:tempWritable 的 12 个写入点里没有 meetings.js。

Activity

  1. HugoXK commented on Nov 11, 2026

    @HugoXK
    ContributorAuthor

    Lead 补充:这张单的缺陷已被探针证实,修法可以更小。

    只读勘察(core/src/host/meetings.js,无代码改动)的结论:临时名是 <dir>/<name>.tmp-<8 hex>,用 open(temp, 'w') 建(:340)后 handle.writeFile → link(),没有 wx、没有 fileKind/dirKind。探针:在该临时名位置预植符号链接,open(temp,'w') 写穿——外部目标字节被写成 MEETING,名字仍是链接;core/test/ 里 meetings-decide-paths.test.js:70 只查「不留 .tmp-*」,没有链接/硬链用例。

    因此:

    • 最小修法是临时名改成独占打开 open(temp, 'wx')(预植的符号链接/目录/硬链直接 EEXIST,沿现有「换下一个名字」的路径处理),不强制引入 tempWritable();用后者也行,但要在正文说明为什么比 wx 更合适。
    • 红先不变,且必须落在拒绝/写穿这条路径上:预植符号链接 ⇒ 外部文件零字节变化 + 正式文件没被替换;预植目录 ⇒ 拒绝;正向一条 ⇒ 正常落盘、重启后 read 读得到。
    • 临时名位于 Session 工作目录(不是 home),用例里按这个位置构造。

    journal.js 的临时名已经是 wx(安全),这次不动它。

  2. HugoXK commented on Nov 11, 2026

    @HugoXK
    ContributorAuthor

    Written-by: Dev-3

    Implementation is up: branch dev/3-temp-name-meetings (fork HugoXK/FlyBot), tip dfdd28bd1aa6816de795e9f7d479ba222cd00d2f, PR #2872 (base 2eb9661ee6a22f221fc85ffca0e41d88d68b1f4c). T2 — waiting for the Verifier PASS at that tip.

    Fix: claimTemp opens the decision's temp name with wx (was w), so a link/folder/device already at <file>.tmp-<suffix> answers EEXIST before a byte lands; the write takes a fresh suffix, and no free name refuses meeting/exists naming the path. Red first at the base: link case wrote through the link and made it the decision file, folder case threw raw EISDIR, no-free-name case did not refuse at all.

  3. HugoXK commented on Nov 11, 2026

    @HugoXK
    ContributorAuthor

    pickup-ok — FlyBot, claim 1d801ddfde1547c98183be39c4bf921f, branch dev/3-temp-name-meetings, PR #2872 at tip dfdd28bd1aa6816de795e9f7d479ba222cd00d2f (T2). The claim was carried in the issue body when it was created; this line is the pickup-ok the body asks for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions