Repository navigation
feat(rooms): the local room invite's plumbing and one shared preview card (#885) - #887
Conversation
…g calls (#876) The Members card and the join dialog need the link to be something the host answers for, not just the envelope layer. `room-invite` reads the live link, `room-invite-mint` and `room-invite-revoke` are the room owner's or a human admin's call (`authz.js`), and `room-invite-ask` knocks at a link someone else handed you: `rt.askLocalInvite` sends `{ op: 'ask', ticket }` and the same knock with `accept: true` on Join, from one of the actor's own Bots whose home has a Bot Social identity — the link cannot name a sender, so the transport does, as on the answering side. A face-room link, a bare ticket and a ticket outside the 16–64 shape are refused in one sentence and send nothing, and a knock carries no `roomId`, so an invitee can never name a room it has not seen. The invite preview card moves into core as one component: `invitePreviewOf` says only what a server reply can honestly say (a name cleaned at fixed point, the lead, the seat count, the deadline), `inviteCountdown` words the deadline, and `RoomInvitePreview` draws the tree from a caller-supplied `h`. Both surfaces render it — the face-room paste flow (`face-link-preview`) and the member-invite dialog (`room-invite-preview`) — through `__flybot__`, because a classic-script plugin cannot import a core module; `kind` renames only the `data-flybot` hooks, so the two surfaces draw the same tree instead of two copies of the markup. Docs: core-host (the endpoints), core-client and plugin-runtime (the shared card), social (the card's one owner). changelog.d/876.
|
Verdict: PASS at 4fafe2c Fresh review of the whole diff against Ran:
Findings: no blocking findings.
Non-blocking notes (follow-ups, not defects in this tip):
This report is a Verifier result, not merge authority on its own; the merge follows AGENTS.md step 6. |
|
Verdict: PASS at c2746e1 Re-review after main moved: #880 (b6ab4b2) conflicted with this branch in the generated Ran:
Findings: no blocking findings. The earlier report's two non-blocking notes still stand (they are picked up in the #884/#876 follow-up). This report is a Verifier result, not merge authority on its own; the merge follows AGENTS.md step 6. |
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.
Written-by: Dev3
Tip SHA: 4fafe2c
Fixes #885
Refs #876
The plumbing behind a local room's own invite link, and the one preview card both surfaces draw. This is the half that was already green: it does not close #876 — the Members card's Invite by link row and the redeem dialog stay there.
Reproduce
On
690093bcthere is no way to ask a room for its link from the client:grep -rn "room-invite" core/src/host/channel.jsanswers nothing: the host hasmintRoomInvite/localInviteOf/revokeRoomInvite/ theaskpath, and no endpoint reaches any of them.rt.askLocalInvitedoes not exist, so pasting a/r/@handle/<ticket>link into the join box can only be refused in one sentence (which is what feat(rooms): answer a local room's invite link (#852) #864 shipped) and never becomes a request.plugins/social/client.js:grep -c "face-link-preview-name" plugins/social/client.jsanswers1on that base, so the second surface that needs a card (the member-invite dialog) would be a copy of it.What changed
core/src/host/channel.js):room-invitereturns the room's live ticket throughrt.localInviteOf(what the card copies and counts down),room-invite-mintandroom-invite-revokewrap the existing mint/withdraw, androom-invite-askknocks at a link someone else handed over through the newrt.askLocalInvite.rt.askLocalInvite(core/src/host/room-membership.js): reads the handle and ticket out of the link withparseRoomInviteLinkand sends{ op: 'ask', ticket }— and the same knock withaccept: truefor Join — from one of the actor's own Bots whose home has a Bot Social identity. The link cannot name a sender, so the transport does, exactly as on the answering side. A face-room link, a bare ticket and a ticket outside the 16–64 shape are refused in one sentence and send nothing; no knock ever carries aroomId.core/src/host/authz.js):room-inviteis a member's read;room-invite-mint/room-invite-revokeneed the room owner or a human admin;room-invite-askneeds a signed-in actor and no authority over anything here, because the ticket is the inviter's record's business.core/src/client/room-invite-preview.js):invitePreviewOfsays only what a server reply can honestly say (a name cleaned at fixed point, the lead,n / m in the room, the deadline),inviteCountdownwords the deadline, andRoomInvitePreviewdraws the tree from a caller-suppliedh. Registered on__flybot__inflybot-slots.jswithinvitePreviewOf/inviteCountdown, because a classic-script plugin cannot import a core module.kindrenames only thedata-flybothooks, so the face-room paste flow (face-link-preview) and the member-invite dialog (room-invite-preview) draw one tree.plugins/social/client.jsloses its own copy of the markup: thefs-preview*inline tree is gone and the face-room dialog renders__flybot__'s component; only thefs-preview*CSS stays.docs/subsystems/core-host.md(the endpoints),core-client.mdandplugin-runtime.md(the shared card),social.md(the card has one owner).changelog.d/876-room-invite-endpoints.added.md.What I ran
All on
4fafe2c5(single commit, 27 files, base690093bc):node --import ./test/harness-resolve.mjs --test --test-concurrency=1 "test/*.test.js"; serial because this machine's pthread limit aborts the parallel run — not a code failure).core/test/remote-room.test.js + authz.test.js + room-invite-preview.test.js→ 79/79.plugins/socialfull suite 386/386, including thehold_reasonassertions Decide a hold on Bot Social's hold_reason, not a guess from reasons (#865) #869 merged and the new "no secondface-link-preview-*markup inclient.js" case.node core/scripts/build-client.mjs --check→ ok;node scripts/ds-bot-patches.mjs check→ 10 patches apply cleanly,core/equalsds-bot/+ patches; core lint 0 errors;changelog31 fragments;arch-docsok.Red without the source change
accept: truethe Join knock carries (room-membership.js)#876: the invitee knocks on the link, and Join is the same knock with accept— only that one (53/54)room-invite-mint/room-invite-revokeauthorization ruleauthz: owner cannot touch another owner Bot or room— only that one (19/20)Per-file diff against
690093bc, checked by file sha rather than by eye:plugins/social/test/social.test.js+19/−0,docs/subsystems/social.md+1/−1 (one sentence of mine replaced), and the only−1lines incore/are theimportline and theROOM_TARGETEDset line. An earlier tip of this branch carried +34/−84 onsocial.test.js— thehold_reasoncases #869 had just merged — and this branch is the rebuild that removed that rollback.Not run (and why)
room-invite-ask`` from the plugin yet, so the offer the inviter sends back (rt.onLocalInviteOffer`) has no surface to draw on. The seam is there and tested at the host level; the plugin wiring is A local room's invite link has no Members-card row and no redemption dialog, and the preview card would be copied a second time #876.Too many attempts. Try again in 60 minutes.).Risk
room-invite-asksends from the actor's own Bot, so it needs at least one Bot with a Bot Social identity on this home; without one it refuses withThis machine has no Bot Social identity yetrather than sending anonymously.core/client.jsis generated: the+84there isnpm run build, andbuild-client --checkis what keeps it honest.RoomInvitePreviewis registered with??=, so a plugin that loaded first and registered its own stays; nothing in this repo does.