Skip to content

host: a notice the host edits never reaches the guests already seated - #1942

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-p292-notice-push
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-p292-notice-push

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1939

Owner: Dev-1
Written-by: Dev-1
Reviewer: Birfy

Base / tip

Base: 564a8bc
Tip SHA: 6fbe848 (the pushed head; its parent is that base and its tree is 0886c74cebb6683ac2e18188c39934c26f92b222, so the run below covers it)
Verified commit: 4fc0d83b293af2b2a9aa01d16db3dd124b3d3269 (tree 0886c74cebb6683ac2e18188c39934c26f92b222; the pushed head is that same tree, rebuilt onto live main by the push path — the branch carries one replay commit, git log upstream/main..HEAD shows only it and merge-base is 564a8bc)
Diff: 6 files, +160/−5

What changed

updateRoom writes a new notice and records the change, but the room-share envelope was only ever pushed when a room is created (notifyRoomShare had exactly one call site, in the join path). So a host that edits or clears the setting wrote the value into its own roster and told nobody: the Bots already seated in the room kept the old notice until they left and rejoined, and clearing it never reached them at all.

The fix is one guarded line in core/src/host/roster.js, after the save and before the return, in the same shape as the join path: when notice !== undefined && notice !== (room.notice ?? '') was true, void rt.notifyRoomShare?.(room, { op: 'join' }). The envelope is the room's current roster — the same object the join path sends, with only the notice field different from the last one the guest received. It is not a new line, not a system message and not an extra event per keystroke: a settings save that leaves the notice alone sends nothing, and only a notice that actually changed sends one envelope.

The guard is the point, so it is its own assertion: updateRoom with a patch that carries no notice, or that writes back the value it already holds, must leave the fake flybotSend untouched — otherwise every membership or admin edit would re-push the notice to every guest.

core/test/remote-room.test.js gains one test (#1939:) that drives two hosts through the real roster: both join, then the host writes {notice: 'New'} with no membership change and exactly one op: 'join' arrives; the guest's applyInboundRoomShare leaves room.notice === 'New'; a following {notice: ''} is the second envelope and the guest's notice is empty. The test also asserts the envelope is the same snapshot with only the notice changed (every other field deep-equals the previous join, room.log length and members unchanged), that {notice: ''} written back at its own value sends nothing, and that a patch touching name/mode/admin but not notice sends nothing. docs/subsystems/core-host.md gains the sentence that owns this behaviour, and changelog.d/1939-notice-push.fixed.md is the user-facing note.

core/test/remote-room.test.js was rebased onto live main, which now carries the fence test additions from #1938 at the same tail of the file: the replay keeps both suites (git diff 564a8bcf HEAD -- core/test/remote-room.test.js is +67/−0, so no upstream case is dropped), and patches/ds-bot/files/test/remote-room.test.js is its byte-identical mirror.

Red first

Both reds were re-run on this rebased tree, each against its own change, and each failed for its own reason.

Reverting the implementation (git checkout 564a8bcf -- core/src/host/roster.js, test file unchanged):

not ok 75 - \#1939: a notice the host edits reaches the guests already seated, once, and only when it changes
  error: |-
    one changed notice is one envelope

    0 !== 1
  code: 'ERR_ASSERTION'
  expected: 1
  actual: 0
# tests 83
# pass 82
# fail 1

Making the push unconditional instead of guarding it (if (notice !== undefined)):

not ok 75 - \#1939: a notice the host edits reaches the guests already seated, once, and only when it changes
  error: |-
    a notice written back at its own value sends no join

    1 !== 0
  code: 'ERR_ASSERTION'
  expected: 0
  actual: 1
# tests 83
# pass 82
# fail 1

Restored afterwards: # tests 83 / # pass 83 / # fail 0 for that file.

Ran

TZ=UTC ARCH_DOCS_BASE=564a8bcf6025c6aec6051968f061ee04fd80b3c1 npm run verify on the committed tree, rebased onto live main 564a8bcf: exit 0. core 2343, social 541, tree 127, timeline 135, workflow 336, .github/scripts 563, interop 37, live-e2e 24 — 0 failures; arch-docs: OK (6 changed file(s)) against that base; lint 0 errors; the gate's own patch legs: ok 10 patches apply cleanly in order, ok core/ equals ds-bot/ + patches/ds-bot/, ok patches/ds-bot/ is what generate writes (generate is a no-op). generate re-ran on the new base and wrote nothing (the stored series is already what it produces), and check/canonical are green standalone.

The first full run of that same command exited 1 on one unrelated, timing-dependent case: core/test/room-external.test.js:79 — a stop is asked for on the protocol before the signal, and a shell is not a program (#198) — timed out waiting for the child after 4.1s under the parallel load. That file alone is # tests 13 / # pass 13 / # fail 0, and the re-run of the whole npm run verify was the exit-0 run quoted above (core 2343/2343). Nothing in this diff touches the external-room spawn path.

Not run: no live host run (the change is host-side state plus one envelope), and no manual two-instance check — the test drives the real roster module with a fake flybotSend and the real applyInboundRoomShare on the guest side.

Checkpoint

{
  "pr": 1942,
  "issue": 1939,
  "claim": "Dev-1",
  "summary": "updateRoom now pushes the room-share envelope when, and only when, the notice actually changed, so a host that edits or clears the setting reaches the guests already seated instead of waiting for them to rejoin. The envelope is the same roster snapshot with only the notice field different, so it adds no line and no extra event. A settings save that leaves the notice alone sends nothing. The test pins both directions plus the guest-side notice after applyInboundRoomShare, and a patch that changes other settings but not the notice sends nothing either.",
  "files": ["core/src/host/roster.js", "core/test/remote-room.test.js", "docs/subsystems/core-host.md", "changelog.d/1939-notice-push.fixed.md", "patches/ds-bot/0002-flybot-api-host.patch", "patches/ds-bot/files/test/remote-room.test.js"],
  "checks": {
    "ran": "TZ=UTC ARCH_DOCS_BASE=564a8bcf6025c6aec6051968f061ee04fd80b3c1 npm run verify, plus both reds on this tree",
    "result": "green",
    "at": "6fbe84888467ed0a85775d1241f5369f858d16fc"
  },
  "next": "none — ready for review"
}

@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict requested for this tip: 6fbe848 (tree 0886c74cebb6683ac2e18188c39934c26f92b222, base 564a8bc)

@Birfy review request

@github-actions github-actions Bot added the risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff label Oct 31, 2026
@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 6fbe848
Written-by: Reviewer
Attestation: 6fbe848 role=Reviewer

Ran at this tip:

$ cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
$ cp /tmp/rv/roster.base.js core/src/host/roster.js && cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/remote-room.test.js   # base blob of core/src/host/roster.js, red-first
# tests 83  # pass 83  # fail 0
# red-first: tests 83  # pass 82  # fail 1 — only "#1939: a notice the host edits reaches the guests already seated, once, and only when it changes" fails; swapped back, 83/83 again

Points:

  • 红先成立:只把 core/src/host/roster.js 换回 base blob 564a8bc,新增的 host: a notice the host edits never reaches the guests already seated #1939 用例就是唯一红的那个;恢复后全绿。归属精确,不是整文件假绿。
  • 范围 6 文件 +160/−5 全在本次修复内:roster.js 一行守卫、core 侧用例、docs/subsystems/core-host.md 一段、changelog.d、以及 patches/ds-bot 的 0002 patch 与 files/ 镜像。核对镜像里 host: a notice the host edits never reaches the guests already seated #1939 用例与 core 侧逐字相同,patch 里 noticeChanged 那行与源码一致。
  • 守卫写法 notice !== undefined && notice !== (room.notice ?? '') 在保存后触发一次 void rt.notifyRoomShare?.(room, { op: 'join' }),与创建/坐下路径同形;用例覆盖两个方向:不带 notice 的 patch(改名、改 mode、交接 admin)不发、回写同值不发、改值发 1 条、清空发 1 条且带显式空串,且不新增 chat 行、不改 members。
  • 客户端侧核过:guest 的 applyInboundRoomShare 确有 notice 替换语义,用例实际把 envelope 灌进去确认了 'New' 与 ''。文档里“settings save that leaves the notice alone — or writes back the value the room already holds — sends nothing”与代码一致。
  • tip check-runs:14 条,除 enqueue-guard 外全 success(tests-ok 14:53Z 绿)。enqueue-guard 红的原因是 no Verifier report names the tip 6fbe84888467,即本结论之前的预期状态,不是代码问题;head 在 fork 上,issue 引用与 agent-pr 规则不适用(标签为空可以)。
  • 提交为单条、parent 正是 base 564a8bc,无夹带。

@github-actions github-actions Bot added the pass label Oct 31, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 31, 2026
Merged via the queue into ThinkFlowLab:main with commit 7f7b42d Oct 31, 2026
13 of 14 checks passed
@Birfy
Birfy deleted the dev/1-p292-notice-push branch October 31, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pass risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host: a notice the host edits never reaches the guests already seated

2 participants