Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Changed

- **Joining a community that vets now offers every way in at once.** The join
flow already asked the 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. It now lists the
ways in — present an invitation, be vetted, or send an open request — each
said to be available or not, and why, with the cursor on the first one that
can actually be taken.

Which way in is open is a fact about the community *and* about this account,
so the invitations held for the community are collected before the page is
drawn rather than two steps later on the identity step. Someone holding an
invitation used to have no way of learning, on the screen that told them the
community vets, that they did not need to be vetted at all.

A route that cannot be taken keeps its row and reads dim with the reason in
place of its detail — "you hold none for this community", "this community
admits nobody by invitation" — because *why not* is the question the page
exists to answer. Taking one says the same thing rather than doing nothing.

An invitation the community marks `required` is not offered as its own row:
it is asked for on top of the statements, not instead of them, so it stays
with the requirements and in the vetting row's detail. An open request admits
that gathered statements ride along with it, which the submit does anyway.

### Fixed

- **A first join no longer sends an open request without asking the community
anything.** Before a community is joined there is no inbound arm to hear a
reply on, so the manifest request the join flow makes from the Communities
panel could not be made at all — and the flow fell through to an open
request in silence. A community that vets refers such a request to its
moderators, and the applicant never learned there was a way in they could
have taken. The flow now says the community was not asked and why, and
offers to join anyway; "ask again" is withheld there rather than offered as
a key that could only ever fail.

### Changed

- **The vetting wire types are the published, generated ones.** `vta-sdk` 0.37
deleted its hand-written copies of the peer-vetting payloads and re-exports
the generated `trust_tasks_rs::specs` types in their place, so what this
Expand Down
10 changes: 7 additions & 3 deletions openvtc/src/state_handler/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,9 @@ pub enum Action {
/// Commit the highlighted context and launch the join in it.
JoinContextChoose,

/// Vetting page: take the highlighted way in. A route the page drew as
/// blocked answers with why instead.
JoinVettingTake,
/// Vetting page: start the application to the community (or continue the
/// one under way) and go to it on the Vetting page.
JoinVettingApply,
Expand All @@ -488,9 +491,10 @@ pub enum Action {
JoinVettingJoin,
/// Vetting page: ask the community for its requirements again.
JoinVettingAskAgain,
/// Vetting page: move the focus (`true` = next field).
JoinVettingField(bool),
/// Vetting page: cycle the focused choice (`true` = forwards).
/// Vetting page: move the highlight down the rows (`true` = next).
JoinVettingRow(bool),
/// Vetting page: cycle the focused "applying as" choice (`true` =
/// forwards). Inert while the highlight is on a route.
JoinVettingCycle(bool),

/// Issue this Active membership's reciprocal VMC (member → community) and
Expand Down
106 changes: 97 additions & 9 deletions openvtc/src/state_handler/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ pub enum JoinPage {
/// Automated mint + join sequence progress / result.
Progress,
/// A community that vets its members: what it requires, in plain words,
/// before anything about the applicant is sent — with the persona's
/// application if there is one, and the ways on (apply, join anyway,
/// cancel). Also the page shown while the community is being asked.
/// before anything about the applicant is sent — and the ways in it leaves
/// open, each said to be available or not and why. Also the page shown
/// while the community is being asked.
Vetting,
}

Expand All @@ -61,12 +61,58 @@ pub enum VettingPhase {
/// answer, draws the page while it waits.
Asking,
/// Its requirements could not be learned; why.
Unknown { reason: String },
Unknown {
reason: String,
/// Whether asking again can work from here. False in the State-A
/// degraded loop, which has no inbound arm to hear an answer on — there
/// the offer would be a key that can only ever fail.
can_retry: bool,
},
/// It vets, and this is what it asks.
Known(Box<KnownVetting>),
}

/// A vetting community's requirements and where this persona stands.
/// A way in to a community that vets.
///
/// The join flow offers all of them at once, available or not, rather than
/// walking one path and leaving the others to be discovered: which way in is
/// open depends on the community's manifest *and* on what this account already
/// holds, and neither is knowable before the community has been asked.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum JoinRoute {
/// Present an invitation (VIC) this account already holds.
Invitation,
/// Be vetted: start an application, carry one on, or present the statements
/// of one that already meets the requirements.
Vetting,
/// Submit with neither; the community's moderators decide.
OpenRequest,
}

/// One way in, as the routes list shows it.
#[derive(Clone, Debug)]
pub struct RouteOption {
pub route: JoinRoute,
/// The row's label.
pub label: String,
/// What taking it does, or where it stands, in a few words.
pub detail: String,
/// `None` when it can be taken now; otherwise why it cannot. A blocked
/// route is still listed — "you cannot use an invitation because you hold
/// none" is the answer to "what are my options", and hiding the row leaves
/// the question open.
pub blocked: Option<String>,
}

impl RouteOption {
#[must_use]
pub fn available(&self) -> bool {
self.blocked.is_none()
}
}

/// A vetting community's requirements, the ways in, and where this persona
/// stands.
#[derive(Clone, Debug, Default)]
pub struct KnownVetting {
/// What it requires, one sentence each.
Expand All @@ -75,14 +121,48 @@ pub struct KnownVetting {
pub governance_url: Option<String>,
/// Our application to it, when there is one.
pub application: Option<JoinApplication>,
/// The ways in, in the order they are offered.
pub routes: Vec<RouteOption>,
/// Personas a new application can be made as.
pub personas: Vec<ApplyAs>,
pub persona_index: usize,
/// Where a new application's face is worn.
pub context_options: Vec<ContextOption>,
pub context_index: usize,
/// 0 = persona, 1 = context.
pub field: usize,
/// The highlighted row: a route, then the two "applying as" selectors when
/// they are shown. See [`selector`](Self::selector).
pub row: usize,
}

impl KnownVetting {
/// Whether the "Apply as" / "Context" selectors are shown. They choose what
/// a *new* application is made as, so an application already under way has
/// answered them — its persona is fixed for its whole life.
#[must_use]
pub fn shows_selectors(&self) -> bool {
self.application.is_none()
}

/// Total rows the cursor moves over.
#[must_use]
pub fn row_count(&self) -> usize {
self.routes.len() + usize::from(self.shows_selectors()) * 2
}

/// The highlighted route, when the cursor is on one.
#[must_use]
pub fn selected_route(&self) -> Option<&RouteOption> {
self.routes.get(self.row)
}

/// Which selector the cursor is on: `0` the persona, `1` the context.
#[must_use]
pub fn selector(&self) -> Option<usize> {
if !self.shows_selectors() {
return None;
}
self.row.checked_sub(self.routes.len()).filter(|i| *i < 2)
}
}

/// A persona an application can be made as.
Expand Down Expand Up @@ -226,9 +306,17 @@ pub struct JoinState {
/// had one; re-pasting a VIC (`JoinPasteVic`) flips it back to `false`.
pub vic_cleared: bool,
/// All valid invitations (VICs) for the community being joined, across
/// personas — collected once after the VTC DID is entered and used to badge
/// each persona with its count on the identity step.
/// personas — collected once the VTC DID is known, and used to badge each
/// persona with its count on the identity step *and* to say on the vetting
/// page whether an invitation is one of this account's ways in.
pub available_vics: Vec<AvailableVic>,
/// The community [`available_vics`](Self::available_vics) was collected
/// for. The collection is a vault listing plus a fetch per descriptor, and
/// several paths now need it (the vetting page before the routes are drawn,
/// the identity step after), so it is done once per community rather than
/// once per caller. An empty result is a legitimate answer, so the marker —
/// not the emptiness of the list — is what says it has been done.
pub invitations_for: Option<String>,
/// The chosen persona's invitations, listed on the
/// [`InvitationChoice`](JoinPage::InvitationChoice) page (a subset of
/// [`available_vics`](Self::available_vics) bound to that persona).
Expand Down
Loading
Loading