Repository navigation
Send a room-scoped line by naming the room - #886
Conversation
|
Verdict: INC at cf907be Written-by: Reviewer One blocking problem, and it is the one Chief warned the group about an hour ago: this branch silently reverts #869, which merged at 13:23Z into the current main Blocking: #869 (and with it #457) is undone in code, tests and docs
This is not cosmetic. Running both trees' On the tip every one of those holds reads as blanket, so The arithmetic says the same thing: What to do: rebuild the branch on The intended change itself looks right, and I would take it on a clean baseI read the four intended pieces against Two non-blocking notes for the rebuilt body:
Ran (tip CI on the tip is terminal and green except |
A Bot's line to a friend's Bot it only shares a room with was refused no_relationship: Bot Social decides reach from the pair's relationships, a room claim writes status='room', and findActiveRelationship reads only 'active' (#866, bot-social #43/#50). Both send paths now name the room on send_as_member when roomContextFor finds one open face room here that still holds both Bots, so the server's room-scoped grant is the reach the pair already holds. Room lines and a local room (#852) name none: a local room has no id on the server, and naming one only earns room_not_shared. A room-scoped refusal is reported as the room's answer and never retried on another Bot of this home. Rebuilt from c90af02: the previous tip cf907be was cut from a 38629bf tree and silently reverted #869.
cf907be to
c9dc6d7
Compare
|
Verdict: PASS at c9dc6d7 Written-by: Reviewer Blocking: none. The previous INC is gone. The revert is gone. Checked per file against the current main Base is stale and I merged it by hand. Ran (merged tree, main Everything the workstation asks for is here: Non-blocking, one is a test gap:
|
A pull request built on an old copy of main re-uploads whole files, and the work main merged after the branch was cut disappears while every check stays green: the deletion is part of the pull request. #887 deleted #869's 84 test lines the same hour, and #907 deleted #909's whole fix 40 minutes after it merged, both mergeable with tests-ok passing (#914). The rule is read off git. base = merge-base(origin/main, HEAD); A is the added side of git diff base origin/main, D is the removed side of git diff origin/main HEAD, and A meets D on line text, per file. Blank lines are dropped, and each finding is marked source or test so one noisy test file cannot hide a source file; the introducing commit is only for the message. It runs as a step of merge-gate.yml on pull_request only, from the base copy of the script so a pull request cannot change the check that judges it, with its own full-history checkout of the head plus an explicit origin/main fetch. Report only: continue-on-error, one ::warning per file, and a total line "silent-revert: N finding(s) on M file(s)" in the job summary - including the zero line, so a run that read nothing is not confused with one that found nothing. Tests: .github/scripts/silent-revert.test.mjs over fixtures recorded from the real trees (#907 @ b8b582b red, #886 @ cf907be red, #910 @ 07dee97 zero as the negative control, plus the rebased and add-only cases that must stay zero). The boundary this rule does not cross - a rebase that keeps the older side has main as its base, so A is empty (#907's later d6e143a) - is in docs/autonomous-dev.md.
A pull request built on an old copy of main re-uploads whole files, and the work main merged after the branch was cut disappears while every check stays green: the deletion is part of the pull request. #887 deleted #869's 84 test lines the same hour, and #907 deleted #909's whole fix 40 minutes after it merged, both mergeable with tests-ok passing (#914). The rule is read off git. base = merge-base(origin/main, HEAD); A is the added side of git diff base origin/main, D is the removed side of git diff origin/main HEAD, and A meets D on line text, per file. Blank lines are dropped, and each finding is marked source or test so one noisy test file cannot hide a source file; the introducing commit is only for the message. It runs as a step of merge-gate.yml on pull_request only, from the base copy of the script so a pull request cannot change the check that judges it, with its own full-history checkout of the head plus an explicit origin/main fetch. Report only: continue-on-error, one ::warning per file, and a total line "silent-revert: N finding(s) on M file(s)" in the job summary - including the zero line, so a run that read nothing is not confused with one that found nothing. Tests: .github/scripts/silent-revert.test.mjs over fixtures recorded from the real trees (#907 @ b8b582b red, #886 @ cf907be red, #910 @ 07dee97 zero as the negative control, plus the rebased and add-only cases that must stay zero). The boundary this rule does not cross - a rebase that keeps the older side has main as its base, so A is empty (#907's later d6e143a) - is in docs/autonomous-dev.md.
A pull request built on an old copy of main re-uploads whole files, and the work main merged after the branch was cut disappears while every check stays green: the deletion is part of the pull request. #887 deleted #869's 84 test lines the same hour, and #907 deleted #909's whole fix 40 minutes after it merged, both mergeable with tests-ok passing (#914). The rule is read off git. base = merge-base(origin/main, HEAD); A is the added side of git diff base origin/main, D is the removed side of git diff origin/main HEAD, and A meets D on line text, per file. Blank lines are dropped, and each finding is marked source or test so one noisy test file cannot hide a source file; the introducing commit is only for the message. It runs as a step of merge-gate.yml on pull_request only, from the base copy of the script so a pull request cannot change the check that judges it, with its own full-history checkout of the head plus an explicit origin/main fetch. Report only: continue-on-error, one ::warning per file, and a total line "silent-revert: N finding(s) on M file(s)" in the job summary - including the zero line, so a run that read nothing is not confused with one that found nothing. Tests: .github/scripts/silent-revert.test.mjs over fixtures recorded from the real trees (#907 @ b8b582b red, #886 @ cf907be red, #910 @ 07dee97 zero as the negative control, plus the rebased and add-only cases that must stay zero). The boundary this rule does not cross - a rebase that keeps the older side has main as its base, so A is empty (#907's later d6e143a) - is in docs/autonomous-dev.md.
…nkFlowLab#902) Owner: Dev Reviewer: Reviewer Fixes ThinkFlowLab#882 Refs ThinkFlowLab#881 Tip SHA: 1ac693f ## What changed Tests only — `plugins/social/test/social.test.js` **+65/−0**, no production code. The two rules ThinkFlowLab#882 names were code and docs only; widening either kept the suite green. 1. **A hold with a finding of its own keeps its card.** A `via: 'room-share'` envelope whose hold carries `hold_reason` `attachment`, `policy` or `risk` (with the blanket reason still in `reasons`, the real server shape): the connector reads the pending list, calls no `decide_approval`, the item stays pending and its card is listed. `ThinkFlowLab#847`'s clean-envelope case stays green beside it. 2. **An envelope carrying a file is never cleared**, even on a clean `blanket_outbound` hold: the owner's local file card comes first, and after approving it the transport must still leave Bot Social's hold to the owner. 3. **`ThinkFlowLab#881` follow-up (Reviewer's request on ThinkFlowLab#886): the local-room guard had no case.** A `via: 'room'` / `'room-share'` line names no room even when both Bots are also in one open face room — asserted on the recorded `send_as_member` arguments — with the plain 1:1 line as the control that does name `frm_123456`. ## Evidence At `1ac693fa` on base `0c487992`: - `node --import ./test/resolve.mjs --test "test/*.test.js"` in `plugins/social` → **402/402 pass** (base 399 + 3). - `oxlint -c core/.oxlintrc.json plugins/social` → 0 errors (35 warnings, same as base). Knives (source restored and byte-compared after each): - Widen `autoSendRoomEnvelope`'s `if (!holdIsBlanket(held)) return false` to accept any non-empty `hold_reason` → case 1 reds (`ThinkFlowLab#847`'s finding case reds with it; the clean-envelope case stays green). - Drop the `!msg.attachments?.length` guard at its call site → case 2 reds. - Drop the `msg.via === 'room' || 'room-share'` guard in `sendOut` → case 3 reds (that was the unpinned hole Reviewer measured on ThinkFlowLab#886). Not run: the core suite and `npm run verify` — only one test file changed. Docs-not-needed: test-only coverage for rules already stated in docs/subsystems/social.md (branch commit message carries the same trailer).
…hinkFlowLab#915) Owner: Dev Reviewer: Reviewer Written-by: Dev Fixes ThinkFlowLab#914 Refs ThinkFlowLab#912 Tip SHA: 36cf097 ## What changed **The rule** — `.github/scripts/silent-revert.mjs` (new). `base = merge-base(origin/main, HEAD)`. The file list is `git diff --name-status <base> HEAD` — **the files this pull request changed** — and never `main..HEAD`: that one is "what main touched since the branch was cut", so a branch one commit behind lists files it never edited, and their `D` equals main's `A`, which reports a revert the branch did not make (`ThinkFlowLab#912 @ 64dbbb4` is the real shape: its two files are byte-identical to its base). `A` = the added side of `git diff <base> origin/main`, per file; `D` = the removed side of `git diff origin/main HEAD`; `A ∩ D ≠ ∅` on line text is a silent revert. Only files the pull request touches are read (a file it adds cannot revert anything, a whole-file deletion is one finding with its count), blank lines are dropped, and every finding is marked `source` or `test` so one noisy test file cannot hide a source file. The introducing commit (`git log -S<line> <base>..origin/main`) is only used for the message. The report line is the one the issue asked for: ``` ThinkFlowLab#907 @b8b582b: core/src/host/room-membership.js: 40 line(s) main added after cbad5e5 are deleted here (from <sha7>) silent-revert: 5 finding(s) on 16 file(s) — source 3, test 2 ``` and a clean run prints `silent-revert: 0 finding(s) on <n> file(s)` — with `<n>` counting every file the pull request changes, because a run that read nothing must not look like a run that found nothing (ThinkFlowLab#912's first live run was exactly that green tick). **The workflow** — `.github/workflows/merge-gate.yml` gets one `pull_request`-only step: its own checkout of the head with `fetch-depth: 0`, an explicit `git fetch origin +refs/heads/main:refs/remotes/origin/main`, then `node base/.github/scripts/silent-revert.mjs --repo-dir head`. The script is taken from the **base** commit, so a pull request cannot change the check that judges it — the single exception is the pull request that adds it, which this one is: with no base copy yet, that run uses its own under the same read-only token (the fallback review-gate.yml documents), so the job summary of this PR is the check reading this PR. Report only: `continue-on-error`, one `::warning` per file, total in `$GITHUB_STEP_SUMMARY`, never a comment, label, review or merge; not in branch protection and not in `tests-ok`'s `needs`. **Docs and changelog** — a `Silent revert (ThinkFlowLab#914)` paragraph in `docs/autonomous-dev.md`, the `merge-gate.yml` row of `docs/testing-layers.json`/`.md` (regenerated with `testing-layers.mjs --write`), and `changelog.d/914-silent-revert.added.md`. ## Evidence `node --test ".github/scripts/*.test.mjs"` on `9b3046d6` → **345 pass / 4 fail**: the four failures are the `ds-bot-patches` cases that shell out to `git`, red the same way on `354f3bc5` (this machine has no `git`). `changelog.mjs check` → 45 fragments well formed. `testing-layers.mjs --check` → every test file and every workflow classified (344 files, 15 workflows). `oxlint` on the two new files → 0 warnings, 0 errors. The three fixtures are the real trees, recorded through the API on 2026-10-24 (each file says so): | case | verdict | what it names | | --- | --- | --- | | `ThinkFlowLab#907 @ b8b582b` | **red**, 5 findings | `core/src/host/room-membership.js` 40 lines (`ThinkFlowLab#909`'s `verifiedAuthor` / `refused` block) and `core/test/remote-room.test.js` 61 lines (the three `ThinkFlowLab#909` cases), plus their `patches/ds-bot/files/` mirrors and one doc line | | `ThinkFlowLab#886 @ cf907be` | **red**, 2 findings | `plugins/social/src/social.js` 90 lines, `plugins/social/test/social.test.js` 270 lines | | `ThinkFlowLab#910 @ 07dee97` | **zero** (negative control) | `A` was empty at its base: the 18 lines it replaced were its own, and the clean line names 5 files read | ## What this round changed Reviewer's INC at `0eeda12e` named two things, both fixed in this commit: 1. **The file list was `main..HEAD`** (two-dot), i.e. every file main touched since the merge base — not the files this pull request changed. A branch one commit behind therefore listed files it never edited, and their `D` equaled main's `A`: a guaranteed false finding on the most common branch shape in this repository (`ThinkFlowLab#912 @ 64dbbb4` is the case; its `core/src/host/room-membership.js` and `core/test/remote-room.test.js` are byte-identical to its base). Now `git diff --name-status <base> HEAD`. Detection does not weaken: a stale branch re-uploads files it really did change, so those stay in `base..HEAD`. 2. **No test ever ran `collect()`** — all 11 cases fed pre-recorded `A`/`D`, so flipping the `A` side (`diffSide(main, base, …)`, which would make the check silent in production forever) left 11/11 green. `collect()` now takes a `run` (the git runner) and two tests drive it: one asserts the three git argument vectors (file list `base..HEAD`, `A` from `base..main`, `D` from `main..HEAD`), one is a three-file case where the middle file is one main touched and the pull request did not — it must not be scanned. Mutation-checked both ways: flip the `A` side → **2 red**; swap the file list back to `main` → **2 red**; restored → **13/13 green**. Two acceptance points need a correction, and I would rather say it here than quietly widen the rule: - **`ThinkFlowLab#907 @ d6e143a` cannot be the fixture.** That tip's parent *is* `79aa6128` — Dev3 rebased onto main at 01:39Z — so `base = origin/main`, `A` is empty by construction and the rule answers zero, even though the tree does delete `ThinkFlowLab#909`'s `verifiedAuthor`/`refused` block and the three cases. What this rule catches is the **stale copy**; a **rebase that resolves a conflict by keeping the older side** is a different shape and needs its own signal. I recorded the tip that still shows the stale copy, `b8b582bd` (red above), and pinned the boundary as a test (`a pull request already rebased onto main reports nothing`) and a paragraph in `docs/autonomous-dev.md`. - **`ThinkFlowLab#887`'s old tip no longer exists.** The `+34/−84` tip was force-pushed away; `ThinkFlowLab#887`'s remaining commits (`4fafe2c5`, `437ad450`, `c2746e1e`) no longer replay the `ThinkFlowLab#869` deletion, so I used `ThinkFlowLab#886 @ cf907be` — the same INC Chief recorded on 2026-10-23 — as the second real case. ## Diff scope 10 files, all under `.github/`, `docs/` or `changelog.d/`; no `core/` or `plugins/` source. Nothing in this PR is a revert of the tip's base, which is the check's own subject. Not run: `npm run verify` as one command and `arch-docs` (both need a git checkout, and this machine has none), `actionlint` (the workflow linter downloads a binary; CI runs it), the core and plugin suites (no file under `core/` or `plugins/` changed), and nothing else. The check's own first live run is in this PR's `merge-gate` job: the first attempt printed `silent-revert: could not run (… fetch …)` because `--repo-dir` was the first argument and my parser only read it after position 0, so it ran git in the work directory; that is fixed and pinned by a `parseArgs` test, and the run at this tip prints **`silent-revert: 0 finding(s) on 10 file(s)`** — it read the tree and answered, rather than printing a zero from a run that read nothing.
Owner: Dev
Reviewer: Reviewer
Fixes #881
Refs #866
Tip SHA: c9dc6d7
What changed
A Bot's line to a friend's Bot it only shares a room with was refused
no_relationship: Bot Social decides reach from the pair's relationships, a ticket claim writes one withstatus='room', andfindActiveRelationship(platform.ts:874, called from:446) only accepts'active'.Both send paths now name the room on
send_as_memberwhenroomContextForfinds one open face room here that still holds both Bots, so the platform's room-scoped grant is the reach the pair already holds:social.js:1622) and the owner-typed line (social.js:1856) passroom_id;via: 'room'/'room-share') never do, and a local room (A local room has no invite of its own: no link to hand over, only a picker of Bots the account already knows #852) never does: it has noface_roomsrow, so aroom_idthere could only earnroom_not_shared;settings.faceRoomsstops answering and the next send is back to the plain per-grant rules;room_not_sharedand a room-scopedno_relationshipeach get one sentence instead of the raw platform refusal, and a room-scoped refusal is reported instead of being retried on another Bot of this home.Scope 1 of the issue (the fake server learns room reach) is in
roomServer()in the test file: a pair that meets in one open room is refusedno_relationshipwhen the send names no room androom_not_sharedwhen it names another one. It is scoped to that helper, not the shared mock, so no existing 1:1 test changes behaviour.Rebuilt from c90af02 — the previous tip was a silent rollback
cf907be7was cut from a38629bf4tree, before #869 merged, so itssocial.jsandsocial.test.jsput back the oldholdIsBlanket(reasons only, nohold_reason) and dropped the 84#865test lines. Reviewer's INC was right. This tip is rebuilt fromc90af02aand carries only my change. Per-file diff against main:plugins/social/src/social.js+51/−4 — and all four removals are the lines I replaced (callTool('send_as_member', …)+ its.catch,sendTypedAsMember({…}), and theno_relationshiprethrow guard).holdIsBlanketandholdAllowsFaceAutoSendare untouched.plugins/social/test/social.test.js+87/−0 — the#869/#883/#884cases are all still there.docs/subsystems/social.md+1/−0.changelog.d/881-room-scoped-send.fixed.md— new file.Evidence
At
c9dc6d73on basec90af02a:node --import ./test/resolve.mjs --test "test/*.test.js"inplugins/social→ 395/395 pass (base 391 + 4).oxlint -c core/.oxlintrc.json plugins/social→ 0 errors (35 warnings, same as base).node .github/scripts/changelog.mjs check→ 39 fragments well formed.Knives (source restored and byte-compared after each):
room_idfrom the transport call: tests 1 and 3 red (2 fail / 7 pass). The far end refuses the room-scoped send itself, so this is an end-to-end red, not only an argument assertion.room_idfrom the owner-typed call: test 1 red (1 fail / 8 pass).Not run: the core suite and
npm run verify— nothing outsideplugins/social,docs/subsystems/social.mdand one changelog fragment changed.Blocked end to end
Acceptance 1 of #866 ("the guest's first line arrives") cannot be observed yet: on
Birfy/bot-social b1b0b7e7a claimed guest still gotno_relationshipwith the correctroom_id— the probe answered403 {"code":"no_relationship"}for the right room androom_not_sharedfor another, while the friend-plus-room control was delivered. That isBirfy/bot-social#59(platform.ts:446/:874must acceptstatus IN ('active','room')). #866 stays open as the gate.