pub struct VtcClient { /* private fields */ }Expand description
A client bound to one VTC’s API base, holding a bearer token once authenticated.
Implementations§
Source§impl VtcClient
impl VtcClient
Sourcepub async fn create_room(
&self,
room_id: &str,
owner_did: &str,
visibility: Visibility,
retention_days: Option<u32>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<Value, VtcError>
pub async fn create_room( &self, room_id: &str, owner_did: &str, visibility: Visibility, retention_days: Option<u32>, signer_did: &str, private_key_multibase: &str, ) -> Result<Value, VtcError>
Register a room with this host.
The caller brings room_id: a room identified by something its host chose could not
move to another host without changing identity.
Sourcepub async fn put_record(
&self,
session: &RoomSession,
key: &str,
sealed: Option<SealedContent>,
cleartext: Option<CleartextContent>,
expected_version: Option<u64>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<PutRecordResponse, VtcError>
pub async fn put_record( &self, session: &RoomSession, key: &str, sealed: Option<SealedContent>, cleartext: Option<CleartextContent>, expected_version: Option<u64>, signer_did: &str, private_key_multibase: &str, ) -> Result<PutRecordResponse, VtcError>
Write a record.
Exactly one of sealed / cleartext — the host refuses the other shape for the
room’s tier, so passing both or neither is a request it cannot honour.
expected_version is an optional precondition: Some(0) means create-only, and
Some(n) requires the stored record to be at version n. A mismatch comes back
carrying the current version, so a caller does not have to re-read to learn what it
lost to.
Sourcepub async fn get_record(
&self,
session: &RoomSession,
key: &str,
signer_did: &str,
private_key_multibase: &str,
) -> Result<Value, VtcError>
pub async fn get_record( &self, session: &RoomSession, key: &str, signer_did: &str, private_key_multibase: &str, ) -> Result<Value, VtcError>
Read one record.
Presents exactly as a write does, and needs no session — which is the point on a sealed room: authorizing reads by session would hand the host a member identifier on every access, and a period of those reconstructs the membership the tier withholds.
Sourcepub async fn list_records(
&self,
session: &RoomSession,
prefix: Option<&str>,
since_version: Option<u64>,
cursor: Option<&str>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<ListRecordsResponse, VtcError>
pub async fn list_records( &self, session: &RoomSession, prefix: Option<&str>, since_version: Option<u64>, cursor: Option<&str>, signer_did: &str, private_key_multibase: &str, ) -> Result<ListRecordsResponse, VtcError>
List record metadata.
Never returns bodies — fetch the handful that matter with VtcClient::get_record.
since_version is the incremental-sync watermark, and the response includes
tombstones: a caller that never saw a retraction would resurrect the record on its
next full rebuild.
cursor continues a previous page. A listing is not complete until the response’s
cursor is absent — a page shorter than the one asked for says nothing, which is
why this takes the token rather than leaving callers to guess from a length.
Sourcepub async fn mint_epoch(
&self,
session: &RoomSession,
epoch: u32,
reason: Option<&str>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<MintEpochResponse, VtcError>
pub async fn mint_epoch( &self, session: &RoomSession, epoch: u32, reason: Option<&str>, signer_did: &str, private_key_multibase: &str, ) -> Result<MintEpochResponse, VtcError>
Advance the room’s key epoch — how a member is removed.
Requires a chain conferring admin. epoch must be exactly one greater than the
current one. The host records the number and never learns the key: distributing it
to the remaining members happens out of its sight.
Sourcepub async fn mint_epoch_with_link(
&self,
session: &RoomSession,
epoch: u32,
link: Option<&EpochLink>,
reason: Option<&str>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<MintEpochResponse, VtcError>
pub async fn mint_epoch_with_link( &self, session: &RoomSession, epoch: u32, link: Option<&EpochLink>, reason: Option<&str>, signer_did: &str, private_key_multibase: &str, ) -> Result<MintEpochResponse, VtcError>
Mint an epoch and hand the host the rung that keeps the room’s past readable.
The rung is the outgoing epoch’s storage key sealed under the incoming one, and minting is the only moment one party holds both — so this is the only call that can carry it. A room that advances without one keeps working and silently loses the ability to read everything written before, for every member including the writer.
Self::mint_epoch is this with None, which is the right call only for a room
that has deliberately chosen not to keep its history.
Sourcepub async fn epoch_chain(
&self,
session: &RoomSession,
from_epoch: Option<u32>,
limit: Option<u32>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<ChainResponse, VtcError>
pub async fn epoch_chain( &self, session: &RoomSession, from_epoch: Option<u32>, limit: Option<u32>, signer_did: &str, private_key_multibase: &str, ) -> Result<ChainResponse, VtcError>
Fetch the room’s epoch key chain, highest epoch first.
What a member calls after joining, or after restoring their group state, so that
records sealed before then still open. Feed the result to
SealedRoom::add_links.
Gated on read at the host: reading the room and reading the parts written earlier
are the same act. The rungs are ciphertext — a caller holding no epoch key learns
nothing from them but how many epochs the room has had.
from_epoch returns only rungs at or below that epoch, which is how a member who
already holds the top of the chain asks for the rest.
Sourcepub async fn curate_record(
&self,
session: &RoomSession,
key: &str,
status: Option<String>,
pinned: Option<bool>,
reason: Option<String>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<CurateRecordResponse, VtcError>
pub async fn curate_record( &self, session: &RoomSession, key: &str, status: Option<String>, pinned: Option<bool>, reason: Option<String>, signer_did: &str, private_key_multibase: &str, ) -> Result<CurateRecordResponse, VtcError>
Change a record’s standing — its status, whether it is pinned, or both.
Needs a chain conferring curate, which is deliberately not implied by write:
deciding what a room’s shared knowledge is worth is a different grant from being
able to add to it.
Separate from Self::put_record because standing is not content. On a sealed tier
a host cannot read what it stores, so “same body, now deprecated” through put would
make a member re-seal and re-upload bytes the host already holds, to say something
that is not about the bytes.
status and pinned are independent: omit either to leave it unchanged, and passing
neither changes nothing. The curation assigns a new version, because a change
others must converge on is a change like any other — one that left the version alone
would be invisible to every sinceVersion watermark in the room.
Sourcepub async fn transfer_owner(
&self,
session: &RoomSession,
new_owner_did: &str,
reason: Option<&str>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<OwnerResponse, VtcError>
pub async fn transfer_owner( &self, session: &RoomSession, new_owner_did: &str, reason: Option<&str>, signer_did: &str, private_key_multibase: &str, ) -> Result<OwnerResponse, VtcError>
Hand the room to another member, deliberately and while still present.
Requires a chain conferring admin — the same grant that mints epochs, since
transferring is the more consequential of the two.
The host does not check that new_owner_did is a member, and cannot: it holds no
roster and no MLS group state. That obligation is the outgoing owner’s, who can see
the group. Give the room to someone who cannot commit and they inherit a room they
cannot renew.
Sourcepub async fn claim_owner(
&self,
session: &RoomSession,
nomination: &str,
reason: Option<&str>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<OwnerResponse, VtcError>
pub async fn claim_owner( &self, session: &RoomSession, nomination: &str, reason: Option<&str>, signer_did: &str, private_key_multibase: &str, ) -> Result<OwnerResponse, VtcError>
Claim a room whose owner has stopped renewing it.
nomination is the succession credential the room issued to this claimant in
advance. All three of the host’s conditions must hold together: a valid nomination,
a room that has been dormant past its grace window — not merely lapsed — and the
claimant’s own membership, which is what session carries.
A claim does not renew the room. It hands over a dormant room, and the new owner’s first act should be the epoch mint that proves they can perform it.
Source§impl VtcClient
impl VtcClient
Sourcepub async fn connect(
base_url: &str,
vtc_did: &str,
client_did: &str,
private_key_multibase: &str,
) -> Result<Self, VtcError>
pub async fn connect( base_url: &str, vtc_did: &str, client_did: &str, private_key_multibase: &str, ) -> Result<Self, VtcError>
Authenticate to the VTC as client_did (challenge-response, reusing the
VTA SDK’s audience-agnostic flow) and return a ready client.
base_url is the full API base including the mount (e.g.
https://vtc.example.com/v1); vtc_did is the community’s DID.
Sourcepub fn with_token(
base_url: &str,
vtc_did: &str,
token: impl Into<String>,
) -> Self
pub fn with_token( base_url: &str, vtc_did: &str, token: impl Into<String>, ) -> Self
Construct a client from an already-obtained bearer token (e.g. a token
minted out of band, or for testing). base_url includes the mount.
Sourcepub fn anonymous(base_url: &str, vtc_did: &str) -> Self
pub fn anonymous(base_url: &str, vtc_did: &str) -> Self
Construct a client with no bearer token, for the applicant side of the join ceremony.
submit_join authenticates with the document’s own
holder proof, so an applicant — who is by definition not yet a member and
has no token to get — needs exactly this. Every other method returns
VtcError::NotAuthenticated, which is the honest answer rather than a
401 from the server.
vtc_did still matters: it is the audience the submitted document is
addressed to, and the VTC rejects a document addressed elsewhere.
Sourcepub async fn list_members(
&self,
role: Option<&str>,
) -> Result<Vec<MemberRecord>, VtcError>
pub async fn list_members( &self, role: Option<&str>, ) -> Result<Vec<MemberRecord>, VtcError>
List every community member, optionally filtered by role, following the
cursor to completion. Requires an admin token. This is the fleet roster
when the community’s members are managed VTAs.
Sourcepub async fn list_join_requests(
&self,
status: Option<&str>,
) -> Result<Vec<JoinRequestSummary>, VtcError>
pub async fn list_join_requests( &self, status: Option<&str>, ) -> Result<Vec<JoinRequestSummary>, VtcError>
List join requests (the admin work queue), optionally filtered by
status (e.g. "pending"). Requires an admin token. For a fleet, these
are VTAs awaiting enrollment.
Sourcepub async fn approve_join(
&self,
request_id: &str,
) -> Result<DecideResult, VtcError>
pub async fn approve_join( &self, request_id: &str, ) -> Result<DecideResult, VtcError>
Approve a join request — admit the applicant and issue its membership credential (VMC). Requires an admin token. For a fleet, this enrolls a VTA that has applied to join.
Sourcepub async fn reject_join(
&self,
request_id: &str,
reason: Option<&str>,
) -> Result<DecideResult, VtcError>
pub async fn reject_join( &self, request_id: &str, reason: Option<&str>, ) -> Result<DecideResult, VtcError>
Reject a join request, optionally recording an operator rationale in the audit trail. Requires an admin token.
Sourcepub async fn remove_member(
&self,
did: &str,
reason: Option<&str>,
) -> Result<RemoveResult, VtcError>
pub async fn remove_member( &self, did: &str, reason: Option<&str>, ) -> Result<RemoveResult, VtcError>
Remove a member (offboarding). The VTC applies its removal disposition and
flips the member’s status-list revocation bit. reason is an optional
admin note. Requires an admin token. For a fleet, this decommissions a
managed VTA.
Sourcepub async fn update_member_extensions(
&self,
did: &str,
extensions: Value,
) -> Result<(), VtcError>
pub async fn update_member_extensions( &self, did: &str, extensions: Value, ) -> Result<(), VtcError>
Update a member’s community-defined extensions (opaque JSON) via
PATCH /members/{did}. A fleet manager records per-member operational
state here — e.g. the assigned fleet_index at enrollment, which the
roster then carries (see MemberRecord::extensions). Admin token.
Sourcepub async fn submit_join(
&self,
body: &JoinRequestSubmitBody,
applicant_did: &str,
private_key_multibase: &str,
) -> Result<VerdictResponse, VtcError>
pub async fn submit_join( &self, body: &JoinRequestSubmitBody, applicant_did: &str, private_key_multibase: &str, ) -> Result<VerdictResponse, VtcError>
Submit a join request (the applicant side): sign a
join-requests/submit/0.1 Trust Task with the applicant’s holder key and
post it to the document endpoint. Returns the community’s verdict —
auto-admit carries the issued VMC + role VEC inline, otherwise the
request is queued for an admin.
No bearer token. The document’s eddsa-jcs-2022 proof is the
authentication: the VTC takes the proof’s verificationMethod DID as the
applicant and requires the document issuer to match it
(vtc-service/src/trust_tasks/mod.rs::resolve_holder). So this is the
one method that works on a client built with neither
connect nor with_token — an
applicant is by definition not yet a member.
applicant_did is a did:key whose seed is private_key_multibase. It
is the DID that becomes the member on admission, not whatever identity
this client may hold a token for — a fleet manager submitting on behalf
of a VTA signs with that VTA’s key.
A holder on any other DID method uses
submit_join_as. This method’s did:key
restriction is a property of this signature — it derives the
verification method from the identifier — and not of the server, which
resolves the proof’s verificationMethod through a DID resolver and has
accepted did:webvh since the vm-resolver work. A did:webvh persona is
the normal case for a holder minted by a VTA, so it must not have to
borrow a did:key to join.
The document is addressed to vtc_did (SPEC §4.8.2
audience binding), so a signed submit captured from one community cannot
be replayed into another.
§Why the key, and not just a body
This used to POST the VP-framed body to POST /join-requests, a route
that no longer exists — the holder-facing join verbs (submit/request,
manifest, status) were folded into the single Trust-Task document
endpoint, routed by document type. That fold moved the applicant’s
authentication from “a signature somewhere inside the body” to “a proof
over the whole document”, which is why this signature grew the key.
Sourcepub async fn submit_join_as(
&self,
body: &JoinRequestSubmitBody,
key: &HolderKey,
) -> Result<VerdictResponse, VtcError>
pub async fn submit_join_as( &self, body: &JoinRequestSubmitBody, key: &HolderKey, ) -> Result<VerdictResponse, VtcError>
Submit a join request signed by a holder of any DID method.
The general form of submit_join, which is now a
did:key convenience wrapper over it. Everything that method’s
documentation says about tokens, audience binding and which DID becomes
the member applies here unchanged; the only difference is that the
verification method is named rather than derived.
A HolderKey names the verification method the proof will carry — for
a did:webvh persona, did:webvh:<scid>:example.com:glenn#key-0. The
server takes that method’s DID as the applicant, so the key must be one
the holder’s published document names: a proof this client signs
happily is still refused if the document does not carry the method.
Sourcepub async fn list_policies(&self) -> Result<Vec<Value>, VtcError>
pub async fn list_policies(&self) -> Result<Vec<Value>, VtcError>
List the community’s policies (opaque JSON descriptors). Admin token.
Sourcepub async fn get_policy(&self, id: &str) -> Result<Value, VtcError>
pub async fn get_policy(&self, id: &str) -> Result<Value, VtcError>
Fetch one policy by id (opaque JSON, incl. the Rego source). Admin token.
Sourcepub async fn upload_policy(
&self,
purpose: &str,
rego_source: &str,
) -> Result<Value, VtcError>
pub async fn upload_policy( &self, purpose: &str, rego_source: &str, ) -> Result<Value, VtcError>
Upload a new Rego policy bundle for purpose ("join", "removal",
…). Returns the upload descriptor (id, sha256, version). Admin token.
Upload alone does not activate it — call activate_policy.
Sourcepub async fn activate_policy(&self, id: &str) -> Result<Value, VtcError>
pub async fn activate_policy(&self, id: &str) -> Result<Value, VtcError>
Activate a previously-uploaded policy (make it live for decisions of its purpose). Admin token.
Sourcepub async fn list_vetter_grants(
&self,
) -> Result<VetterGrantListResponse, VtcError>
pub async fn list_vetter_grants( &self, ) -> Result<VetterGrantListResponse, VtcError>
Every vetter grant, newest first (GET /vetting/vetters). Admin token.
Each row carries the member, validity, revocation, whether it is live, whether an admin or the automatic sweep issued it, and the vetter’s profile summary.
Sourcepub async fn grant_vetter(
&self,
grant: &Payload,
) -> Result<VetterGrant, VtcError>
pub async fn grant_vetter( &self, grant: &Payload, ) -> Result<VetterGrant, VtcError>
Name a current member a vetter (vtc/vetting/vetters/grant/0.1, over
POST /vetting/vetters). Admin token.
grant is the task’s payload: validitySeconds is one day to two
years, and absent takes the community’s default of one year. A member
already holding a live grant gets that grant back with
VetterGrant::created false.
Sourcepub async fn revoke_endorsement(
&self,
endorsement_id: &str,
) -> Result<EndorsementRevocation, VtcError>
pub async fn revoke_endorsement( &self, endorsement_id: &str, ) -> Result<EndorsementRevocation, VtcError>
Revoke an endorsement by id (vtc/endorsements/revoke/0.1, over
DELETE /credentials/endorsements/{id}) — how a vetter grant is
withdrawn. Admin token. Revoking a grant also deletes the vetter’s
profile.
Sourcepub async fn resend_vetter_grant(
&self,
member_did: &str,
) -> Result<Response, VtcError>
pub async fn resend_vetter_grant( &self, member_did: &str, ) -> Result<Response, VtcError>
Deliver a vetter’s live grant credential again
(vtc/vetting/vetters/resend/0.1, over
POST /vetting/vetters/{memberDid}/resend). Admin token.
Success means the community handed the credential to its messaging transport — not that the member’s wallet has it. A member with no live grant is a 404; a transport that would not take the delivery is a 503.
Sourcepub async fn auto_grant(&self) -> Result<AutoGrantStatus, VtcError>
pub async fn auto_grant(&self) -> Result<AutoGrantStatus, VtcError>
The automatic vetter-grant configuration and the last sweep
(GET /vetting/auto-grant). Admin token.
Sourcepub async fn configure_auto_grant(
&self,
config: &AutoGrantConfig,
) -> Result<AutoGrantStatus, VtcError>
pub async fn configure_auto_grant( &self, config: &AutoGrantConfig, ) -> Result<AutoGrantStatus, VtcError>
Replace the automatic vetter-grant configuration
(PUT /vetting/auto-grant). Admin token. An absent member takes its
default, so read the current configuration first to change one value.
Sourcepub async fn branding(&self) -> Result<CommunityBranding, VtcError>
pub async fn branding(&self) -> Result<CommunityBranding, VtcError>
How the community presents itself to an applicant’s client
(GET /community/branding) — the join manifest 0.2 branding. Admin
token.
Sourcepub async fn set_branding(
&self,
branding: &CommunityBranding,
) -> Result<CommunityBranding, VtcError>
pub async fn set_branding( &self, branding: &CommunityBranding, ) -> Result<CommunityBranding, VtcError>
Replace the community’s branding (PUT /community/branding) and return
what was stored. Admin token. Every member is optional; an absent member
is cleared.
Sourcepub async fn vetting_revocations(
&self,
) -> Result<Vec<VettingRevocation>, VtcError>
pub async fn vetting_revocations( &self, ) -> Result<Vec<VettingRevocation>, VtcError>
Every vetting statement withdrawal notice, newest first, with the
admissions each touches (GET /vetting/revocations). Admin token.
Trait Implementations§
Source§impl Debug for VtcClient
Written by hand rather than derived, for two reasons.
impl Debug for VtcClient
Written by hand rather than derived, for two reasons.
The first is required: vta_sdk::client::VtaClient is not Debug, so a
derive stops compiling the moment a session is held.
The second is the one worth keeping. The derive printed token — the bearer
token, in full, into anything that formatted this struct: a tracing field,
a test failure, an unwrap on an enclosing type. A credential that reaches a
log is a credential that has left, and nothing about the derive said so. The
presence of a token is worth reporting; its value never is.