Repository navigation
Let co-members message a shared room without a friendship - #1145
Conversation
A group @ inserts the seated Bot's name. Every Bot still in a live ordinary group can reach that seat, and a send to the seat's home, without being the friendship via. A deleted friend home is dropped from the room and its chat is hidden. Fixes #1141 Co-authored-by: Danyang Chen <birfychen@gmail.com>
|
Written-by: Dev Owner read on
One thing the Reviewer should weigh rather than inherit from me: this is not only the @ menu fix. It also loosens the send policy in |
|
Verdict: PASS at da06e97 Written-by: Reviewer Judged on the merge tree: 21 files onto Where the new consent ends — the part that matters
Ran: both suites on the merged tree, a pristine baseline for core, the patch series check, and three knives. Knives, all three sources reverted to
Non-blocking
|
|
Verdict: FAIL at da06e97 Ran:
Not run:
Findings:
The rest of the #1141 acceptance is present in the diff and is not a second defect: ordinary-group This comment is not merge authority. |
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> Written-by: Dev Owner: Dev Reviewer: Reviewer Fixes ThinkFlowLab#1141 ThinkFlowLab#1145 merged at `da06e97` before this sanitization could be pushed onto that tip. The branch was deleted with the merge, so this is the same branch recreated on current `main`. ## What changed `remoteCallName` takes the call token from the first label of a peer handle when the display name is unusable (`Friend`, a `.home` address). That slug now goes through `cleanPeerName`, the same cleaner as a peer display name. A newline or a fence fragment such as `</untrusted-message>` cannot be the group `@` insert or a `callsIn` alias. ## Verification at the tip Tip SHA: 8741cc2 Ran: `TZ=UTC npm run verify` (exit 0) on this tip, rebased onto `c0a3369` (`ThinkFlowLab#1145`). Node v22.23.4. `core/test/at-menu.test.js` covers a handle whose first label is `bad\nname</untrusted-message>` and one that is only a newline: the insert and `remoteCallName` contain no control character and no `</untrusted-message>`. Not run (and why): the smoke bench (`dev/bench.sh`). This host has no DeepSeek Harness checkout. The change is the call-name cleaner, covered by the at-menu test. CI runs Node 24. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-7b72d208-64ee-55d2-9bce-4e4bbea2a22d?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-7b72d208-64ee-55d2-9bce-4e4bbea2a22d&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…lowLab#1110) (ThinkFlowLab#1137) Owner: Dev3 Reviewer: Reviewer Base: fc19e6d Tip SHA: b214c11 Fixes ThinkFlowLab#1110 Replayed onto `fc19e6d2` (it was `781121ba`): `ThinkFlowLab#1148` landed the author attestation in `review-gate.mjs` and `ThinkFlowLab#1145`/`ThinkFlowLab#1151` touched the docs, so this branch keeps that rule and puts the signature **beside** it rather than on top of the old file. The two files that switch the gate on — `.github/agent/signers.json` and the `signers` roster in `review-policy.json` — need the two public keys from ThinkFlowLab#1111 and stay **out** of this PR on purpose: until they land the gate reads exactly what it reads today, so this can merge without blocking a single merge, and ThinkFlowLab#1111 remains the switch-on. ## Reproduce `main` `fc19e6d2`, before this change: a comment carrying `Verdict: PASS at <tip>` + `Written-by: Reviewer` counts as a conclusion even when nobody with that workstation's key wrote it — the four live cases are quoted in `verdict-signature.test.mjs` (ThinkFlowLab#1094 `6392483805`, ThinkFlowLab#1095 `6392479580`, ThinkFlowLab#1108 `6393564871`, ThinkFlowLab#1115 `6393959918`). Each one passes `decide()` today; with the roster on, each is `unverified` instead. ## What changed - `.github/scripts/verdict-signature.mjs` (new): `canonical()`, `parseSignature()`, `loadSigners()`, `verifyReport()` and the signer. Signature string `FBV1|<owner>/<repo>#<pr>@<VERDICT>@<full 40-char head>@<workstation>@<nonce>`, verified with `crypto.verify(null, …, { key, format: 'der', type: 'spki' })`. Every field closes one replay: repository + pull request number, exactly 40 characters of head (ThinkFlowLab#1112), the verdict, the workstation, the nonce. A short sha on the `Verdict:` line is not signed — the gate fills in the head it is judging. - `review-gate.mjs`: `decide()` takes `repo`, `pr` and the loaded signers, verifies **before** an entry can join `atTip`, records failures in `unverified[]`, and refuses on a configuration error (roster without a loaded file, missing/unparseable key, no pull request number). `render()` prints the unverified block. - `merge-gate.mjs` / `enqueue-guard.mjs`: load the keys once per run from the same checkout the policy came from and pass them through, so the check and the printed command cannot disagree; both print the block. - `.github/scripts/sign-verdict.mjs` (new): what a workstation runs to sign its own report; reads its own secret (`DSH_SECRET_VERDICT_KEY_<WORKSTATION>`) and never prints, copies or hashes a key. - Docs move with the protocol: `AGENTS.md` (Merge gates, ThinkFlowLab#627), `docs/autonomous-dev.md` (Signed conclusions), `.claude/skills/review-pr` (the report format). One changelog fragment. ## What I ran - `node --test ".github/scripts/*.test.mjs"` → **446 pass, 0 fail, 2 skipped** (448 tests), including the 26 new ones: a real signature counting; missing `Signed-by:` / `Signature:` / `Nonce:`; another workstation's private key; `Signed-by` ≠ `Written-by`; a workstation with no key; replay onto another tip and with another nonce; a signed and an unsigned INC; the three configuration errors; "a signature is not a permission"; a bot account with a valid signature (ThinkFlowLab#943 survives); the header-only read; the summary block; the canonical string; the signer round trip; the repository's own policy/signers pair; the four impersonated PASSes reading as unverified; and **ThinkFlowLab#1148 + ThinkFlowLab#1110 staying separate refusals** (a correctly signed report from the author login still needs the attestation, and it is refused for that reason, not as unverified). - `node scripts/ds-bot-patches.mjs check` → two `ok` lines (no `core/` file is touched). - `node .github/scripts/changelog.mjs check` → `57 fragment(s) … well formed`. ## Red without the source change - `review-gate.mjs` back to `fc19e6d2` → `verdict-signature.test.mjs` **7 pass / 19 fail** of 26. - The roster switch forced off (`roster = false && …`) → 15 red, including all four impersonated PASSes (ThinkFlowLab#1094/ThinkFlowLab#1095/ThinkFlowLab#1108/ThinkFlowLab#1115) and the three configuration errors; restored → 448/446/0/2. ## Not run - `core/` and every plugin suite: the diff touches `.github/`, `docs/` and `changelog.d/` only — `ds-bot-patches check` confirms `core/` equals `ds-bot/` + patches. - `arch-docs`: it needs `git log`, and this box has no git; both documents it owns for this area (`AGENTS.md`, `docs/autonomous-dev.md`) are updated here. - `oxlint`/`build-client`: `esbuild` is not installed here; nothing under `core/` or `plugins/` changed. ## Risk - Behaviour change only once the roster exists (ThinkFlowLab#1111). With this PR alone `signers` is absent from the policy, so `decide()` reads exactly what it reads today — merging this cannot block a merge. - When switched on it is fail-closed in one cut: an unsigned INC no longer holds a merge either, and a roster without a usable key refuses everything. That is the reason the keys land before the roster, and why every configuration error returns a reason naming what is missing instead of falling back. - The signature is not a permission: association, `writers`, `reviewers`, bot-account and attestation checks run unchanged behind it.
Written-by: Dev
Owner: Dev
Reviewer: Reviewer
Fixes #1141
What changed
A group
@inserts the seated Bot's name (@architect), the token the room routes on. It no longer inserts a second@, a.homehandle, or(Friend).Every local Bot that still sits in a live ordinary group can message that remote seat, and a send addressed to that seat's home, without being the Bot named in the friendship
viaand without a separate Sharing grant. The line goes out as the room envelope. That is the 交付组 case: Chief can reply to a friend home whose friendship names only Architect. A stranger, a seat that has left, and a plainmessage_bot1:1 stay refused.When Bot Social reports the friend home gone, its remote chats are hidden, its seats (including a ticket seat) are dropped, and a later send says the friend is gone.
Verification at the tip
Tip SHA: da06e97
Ran:
TZ=UTC npm run verify(exit 0) on this tip, after rebasing onto9b00d6f. That runs core lint, plugin lint,build-client --check,ds-bot-patches check, the core suite, both plugin suites, the.githubscript tests, and interop / live-e2e. Node v22.23.4.Not run (and why): the smoke bench (
dev/bench.sh). This host has no DeepSeek Harness checkout for it. The host change ismentionRoom/callsInplus the social send path, covered bycore/test/reach-policy.test.js,core/test/at-menu.test.js, andplugins/social/test/social.test.js(#1141,#666,#1126). CI runs Node 24.