Skip to content

feat(join): offer every way in to a vetting community at once - #342

Merged
stormer78 merged 1 commit into
mainfrom
worktree-join-routes-page
Sep 18, 2026
Merged

stormer78 merged 1 commit into
mainfrom
worktree-join-routes-page

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Why

Joining a community that vets read as a dead end. The flow already asked the
community for its manifest before anything about the applicant was sent — but
all it did with the answer was print the requirements, so "this community vets"
came across as "go to the Vetting page first", even to someone holding an
invitation that would have admitted them without being vetted at all.

The invitations were the specific miss: they were only collected in
continue_join, after the vetting decision, so the page that told you the
community vets had no way of knowing you already held a way past it.

What changed

The vetting page is a chooser, not a requirements sheet. It lists the ways
in — present an invitation, be vetted, send an open request — with the cursor on
the first one that can actually be taken. ↑/↓ moves, Enter takes it; A opens
the application and J joins now, as before.

A route that cannot be taken keeps its row. It reads dim with the reason
where its detail would be ("none held for this community", "Kernel does not
admit anyone by invitation"). Hiding it would leave why not unanswered, which
is the question the page exists to settle. Pressing Enter on one says the same
thing rather than doing nothing — issue #29's rule.

Invitations are collected before the routes are drawn. ensure_invitations
does it once per community, keyed on a marker on the join state rather than on
the list being empty: holding none is a legitimate answer, not a cache miss.
continue_join and the resume-from-manifest path go through it too, so the
vault listing does not happen twice on a path that already did it.

Two manifest shapes the page now distinguishes:

  • invitation: "required" is asked for on top of the statements, so it gets no
    row of its own — it stays in the requirement bullets and in the vetting row's
    detail. A row would read as a way around being vetted.
  • invitation: "none" blocks the row with that reason, even for someone holding
    one, rather than offering something the community will refuse.

An open request admits that statements ride with it whenever the joining
persona has gathered any, because the submit attaches them without asking.

Fixed

A first join never asked the community anything. The State-A degraded loop has
no inbound arm — a documented invariant — so hears_replies is false there and
the manifest request could not be made; the flow then fell through a _ arm
straight to an open request, in silence. A community that vets refers that
request to its moderators, and the applicant never learns there was a way in they
could have taken. The page now says the community was not asked and why, and
withholds "ask again" there instead of binding a key that could only ever fail.

Not in this PR

Choosing to be vetted still leaves the join: JoinVettingApply starts the
application and returns to the Vetting panel, so the community has to be entered
again afterwards. Suspending and resuming the join is the follow-up.

Testing

  • cargo fmt --all, cargo clippy --all-targets --all-features — clean
  • cargo test --all-features -- --include-ignored — all green, including the
    MockVta e2es only CI's coverage job normally runs
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features — clean

New tests cover the routes builder (invitation in hand is first and available;
holding none keeps the row with its reason; required yields no standalone row;
none blocks it even while holding one; applying needs a persona but an open
request does not; an open request names the statements that go with it) and the
page's keys (Enter takes the highlighted route whatever it is, and "ask again" is
neither drawn nor bound when no answer could be heard).

Live run against a vetting community not yet done.

The join flow already asked a community what it required before anything
about the applicant was sent. What it did with the answer was print the
requirements and leave the applicant to work out what to do with them —
so "this community vets" read as "go and get vetted", even to someone
holding an invitation that would have admitted them without it.

The vetting page now lists the ways in as a chooser: present an
invitation, be vetted, or send an open request. Each row says whether it
can be taken and, when it cannot, why — a blocked route keeps its place
and reads dim with the reason where its detail would be, because *why
not* is the question the page exists to answer. Taking a blocked route
says the same thing rather than doing nothing (issue #29's rule).

Which way in is open depends on the community's manifest *and* on what
this account holds, so the community's usable invitations are collected
before the routes are drawn rather than two steps later on the identity
step. `ensure_invitations` does it once per community, keyed on a marker
rather than on the list being empty — holding none is an answer.

Two shapes the manifest distinguishes and the page now does too: an
invitation marked `required` is asked for on top of the statements, not
instead of them, so it is left to the requirement bullets and the vetting
row's detail rather than offered as a row that reads like a way around
vetting; and a community that admits nobody by invitation says so, even
to someone holding one.

Also: a first join could not ask the community anything at all. The
State-A loop has no inbound arm, so there was no reply to hear — and the
flow fell through to an open request in silence, which a vetting
community refers to its moderators. It now says the community was not
asked and why, and withholds "ask again" there rather than binding a key
that could only ever fail.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 18, 2026 07:53
@stormer78
stormer78 merged commit a9d168a into main Sep 18, 2026
15 checks passed
@stormer78
stormer78 deleted the worktree-join-routes-page branch September 18, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant