Skip to content

feat(join): tell apart the two ways of holding no invitation - #352

Merged
stormer78 merged 1 commit into
mainfrom
feat/invitation-two-situations
Sep 19, 2026
Merged

stormer78 merged 1 commit into
mainfrom
feat/invitation-two-situations

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Follow-up to #351, applying the same rule to the row it left inconsistent. Single commit on main.

The observation

A VIC can only be issued to a persona DID that exists. So no persona implies no
invitation is possible
; personas existing means one might exist.

That is provable, not a heuristic. validate_invitation_credential (in
openvtc-core/src/join.rs) requires credentialSubject.id — a credential
without one is refused — so there is no such thing as a bearer invitation, and
that subject is a persona DID.

What was wrong

"Use an invitation" showed one row for both situations: dim, reading "none held
for this community — paste one on the invitation step if you have one the vault
has not seen"
. A greyed-out row telling you there is a way to act on it, which
is the shape of confusion the routes list exists to remove — the same objection
that produced #351, left half-applied.

Now

No persona Blocked"an invitation is issued to one of your personas, and you have none yet"
A persona, none in the vault FirstStep"none is in your vault, so this starts by asking you to paste one — if you have none, the step offers to join without it"
One held Ready
Community takes none Blocked

A bug this caught in #351

RouteState::FirstStep carried only a note, and the take handler opened the
create-persona overlay for any route that announced a first step. That was
fine while the vetting route was the only one — and would have been wrong the
moment the invitation route announced one, which is this PR.

The invitation's first step is not an overlay; it is the page the route already
leads to. So FirstStep now carries FirstStepKind:

FirstStep { note: String, kind: FirstStepKind }

enum FirstStepKind {
    CreatePersona,  // the join does something first, then takes the route
    OnTheWay,       // nothing extra — the step is a page the route leads to
}

Inferring that from the route's identity would have put the same reasoning in
two places and got it wrong in one of them.

Testing

  • cargo fmt --all --check, cargo clippy --all-targets --all-features — clean
  • cargo test --all-features -- --include-ignored — all green
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features — clean

Re-run after rebasing onto main with #351 in it.

  • holding_no_invitation_is_shut_without_a_persona_and_a_step_with_one — both
    halves, in one test, so they cannot drift apart.
  • the_two_first_steps_are_different_steps — pins the kinds, which is the
    assertion that would have caught the overlay bug above.

"None held for this community" covered two situations that call for
opposite treatment, and showed the row dim while suggesting you paste one
on a later step — a greyed-out row telling you there is a way to act on
it, which is exactly the confusion the routes list exists to remove.

Which situation it is follows from the credential rather than from a
guess. A VIC must name a `credentialSubject.id`:
`validate_invitation_credential` refuses one without it, so there is no
such thing as a bearer invitation, and that subject is a persona DID.

So with no personas, no valid invitation can be *for* you — there is not
even a DID one could have been issued to — and the row is genuinely shut,
saying that rather than "none held". With a persona, one may well exist
that this vault has not seen, and pasting it is a step the join can walk
you through, so the row is live.

That in turn exposed something the previous change would have shipped
wrong: the take handler opened the create-persona overlay for *any* route
that announced a first step. The invitation's first step is not an
overlay, it is the page the route already leads to. `FirstStep` now
carries its `FirstStepKind` — `CreatePersona` or `OnTheWay` — because
inferring it from the route's identity would put the same reasoning in
two places and get it wrong in one of them.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 19, 2026 06:54
@stormer78
stormer78 merged commit 04a6767 into main Sep 19, 2026
15 checks passed
@stormer78
stormer78 deleted the feat/invitation-two-situations branch September 19, 2026 06:59
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