OpenVTC — The Trust Community CLI

Repo: github.com/OpenVTC/openvtc

OpenVTC is the user-facing tool for participating in Verifiable Trust Communities. It’s a Rust CLI and TUI (terminal user interface) that orchestrates identity creation, relationship building, and credential exchange — making the complex machinery of decentralized trust accessible to end users.

What It Does

OpenVTC implements the First Person Protocol for the “Know Your Developer” use case. From a user’s perspective:

  1. Set up your account — bind the TUI to your VTA (a managed one on the VTA Farm or your own — see vti-setup) and a Trust Context on it; since June 2026 setup mints no persona. Since Dogwood, setup looks before it writes: pointing a new install at a context that already holds an account offers to recover it from what the VTA holds, rather than silently minting a second set of personas beside the first
  2. Manage your own identity — the My Identity pane (September 2026): mint Persona DIDs (did:webvh, with a chosen or server-allocated path, in a VTA context of your choosing), optionally claim a human-readable agent name (example.com/@alice), and — one layer up — curate the pool of facts (attributes) behind those personas, the faces (profiles) that group them, which face each community is shown, and the record of what has actually been disclosed
  3. Connect with people — send and accept relationship requests over TSP or DIDComm; since Dogwood a new relationship defaults to a pairwise R-DID, and the VRC it produces is issued under that R-DID rather than the persona
  4. Build your trust network — exchange Relationship Credentials, receive endorsements, get witness attestations
  5. Participate in communities — join VTCs by DID or agent name, and by whichever way in the community offers: present a Verifiable Invitation Credential, be vetted by existing members (peer-identity-vetting — by named vetters, or since October 2026 by hidden vetters, whose vouching is proven to the community with a zero-knowledge proof that never names them), or send an open request for admin approval. Since 0.5.0 every join ends on a review page that lists exactly what the community will receive, and then asks — unticked by default — whether the community may publish your membership in its Trust Registry. Belong to multiple communities at once, each under its own persona in its own VTA sub-context; renew a membership (R), rescue an approved join whose credential never arrived, see which capabilities each community has enabled and why a membership was rejected or removed, and grant a second device access to one community only
  6. Vet others — if a community has named you a vetter (a revocable vetter role credential, a DTG v1 Authority Credential), hand out tickets (QR included), run a guided session as a step-by-step journey against an applicant’s Vetting Card, and sign vetted/1 Vetting Statements from the Vetting desk — or, where the community hides its vetters, spend an attestation token drawn on a schedule and attest without your DID ever reaching the community
  7. Assert personhood — answer a community’s personhood ceremony over Trust Tasks, matching a spoken eight-character code with the administrator in the room, so the PHC claim lands on your VMC
  8. Work in the community’s repositories — the Repos panel (r on a community, since 0.4.0) shows the git namespaces a community governs on GitHub or Forgejo (community-git-namespaces): which repositories you may create, own, maintain or sign for, who else is on each, and drift the community’s bridge has reported. Since 0.5.0 it also clones checkouts, sets up did-git-sign for the persona without did-git-sign init or a pnm grant, and makes a checkout sign — with the VTA doing the signing over keys/sign-sshsig, so the private key never leaves it

Behind the scenes, OpenVTC orchestrates the VTA (for key management, the credential vault, and — increasingly — as the system of record for the account), TSP/DIDComm messaging on the TDK’s reliable delivery layer (for communication), and Trust-Task documents carried in the binding envelope (for every protocol exchange). Since Eucalyptus-RC-0 it speaks TSP Rev 3, forming and persisting the §7.2.2 relationship a VTC requires before any application traffic is accepted. Since 0.4.0 every Trust Task request it sends is signed as the persona (the authentication key for acts on one’s own standing, the assertionMethod key for claims to be held to later) and every credential it receives is verified — issuer proof, validity window, revocation status, fail-closed — before it is stored: “decisions about who said something rest on a proof, not on the sender”.

Components

The workspace is three crates, all publish = true since #422 (2026-10-03, with docs/releasing.md describing a manual release), though nothing has yet been pushed to crates.io. After sitting at 0.3.1 from 2026-08-16 through Cypress, VTI-Dogwood and VTI-Eucalyptus-RC-0 while some 2,400 lines of [Unreleased] CHANGELOG accumulated, the version moved twice in the Eucalyptus window: 0.4.0 (tag v0.4.0, 2026-10-04, #424 — “the vetted-admission release”, folding in everything since 0.3.1) and 0.5.0 (2026-10-05/06, #443 and #481 — “the guided-vetting release”; a minor bump because openvtc_core::join::JoinPresentation gained a field; the CHANGELOG dates it 2026-10-06 and there is no v0.5.0 tag). VTI-Eucalyptus (2026-10-07) ships openvtc 0.5.0. The coordinated tree-named tags (coordinated-releases) still mark the stack-wide release; the crate version once again marks the client’s own.

openvtc

The user-facing TUI binary (formerly openvtc-cli2). The unsuffixed name is intentional, matching the convention used by uv, ruff, deno, and cargo. Main-menu panels (in menu order, as of 2026-10-07):

  • Communities — the landing page: memberships with favorites, a Ctrl+K switcher, join (j — by community DID or agent name; the flow reads the community’s requirements from the endpoint it publishes, speaks join-requests 0.3 and falls back to 0.2, and lists every way in — invitation, vetting, open request — saying which are open to this account and why; a persona or a face can be made without leaving it, and a second persona can apply), review before sending and the Trust registry consent page (0.5.0), per-community Capabilities panel (c), the Repos panel (r, see below), renew a membership or rescue an approved join whose credential never came (R), leave/archive, delete a finished community’s VTA context (D), per-community device access (g), each row’s “wears: face” clause (with what the community is allowed to call it) and how the membership is kept apart (own context / shared / top), and a Reason: block for rejected or removed memberships. Joins still being vetted are listed; an ended join can be applied for again
  • Inbox — real-time task processing (auto-handles trust-pongs, relationship finalization, rejections; queues interactive tasks; detail views for inbound/outbound requests, VRCs, pings, informational messages); a accepts a relationship request pairwise, p under the persona DID; a vetting task opens on the journey step where it is taken
  • My Relationships — list/detail/new-request views, inline alias editing, the pairwise-vs-persona toggle with its consequences spelled out (seeded from the community’s relationshipIdentifierDefault), trust-ping with RTT latency
  • My Credentials — Received/Issued tabs plus, since 0.4.0, a Vetting tab for every statement a vetter has issued to you; the list leads with what each credential is (Membership, Role: vetter, a statement), which persona holds it and a readable validity, with PQC-SIGNED and PCS ZKP badges where they apply; raw JSON in detail view, clipboard copy, VRC request and removal
  • Vetting (since September 2026) — two tabs: Applications (be vetted: one per community, each opening as a full-width journey — Requirements → Face → Vetters → Sessions → Statements → Join — with a step strip, a “What’s happening” explainer and only the current step’s keys; abandon with x) and the Vetting desk (vet others: headed by the vetter role credential that names you, with Requests / Tickets / Issued sub-views, each request its own journey — Request → Session → Match code → Card → Check → Sign — attest or decline with an optional private reason, withdraw a statement, QR tickets, your vetter profile and the directory). For communities that hide their vetters the desk header shows your token balance, k gets tokens, e requests event-mode tokens for a conference, and h opens the Hidden vetting view (enrollment, tokens held / usable / spent, live labels, rate and tick length, next window, last refusal)
  • My Identity (since September 2026) — five tabs: Personas (mint with a chosen DID path and context; agent names), Your facts (the attribute pool; values fetched only on request; sensitive facts masked and fetched one at a time; purge earlier versions), Faces (profiles over the pool, with a cursor to reveal a claim; retire and reinstate a face, read where it has been worn and told; faces made inside a community), Communities (which face each community is shown and what it calls it; make a face for a community right there; the People a persona has heard from; who the community knows you as), What has left (the disclosure record, and the Formats a disclosure can take). Worlds are offered once a holder has four faces. Personas used to live on the VTA panel; “identity is not a property of the agent that hosts its keys”
  • Settings — inline editing, config export/import, passphrase protection management, hardware token detection, Theme picker with live preview, factory reset (naming the pnm contexts delete <id> to run); mediator DID is read-only until a persona exists. The hard-coded “Org DID” row is gone (#459)
  • VTA Service — “about the agent again”: transports the VTA advertises (TSP / DIDComm / REST), the VIC list, key count, backend type, and since 0.5.0 a Replies row that notices an admin session whose replies have stopped arriving and shows the rebuild
  • TSP Relationships (since Eucalyptus-RC-0) — each joined community’s TSP Rev 3 §7.2.2 relationship state (None / Pending / Invite received / Bidirectional); a different keyspace, lifecycle and wire from the DIDComm relationships model
  • Logs — scrollable, transport-attributed, 200-entry durable activity log with copy; listener lines carry the listener id and the speaking persona, and a mediator’s scheduled reconnect is one calm line
  • Help/Status, Quit

The Repos panel (r on an Active community; #377, #379, #384, #391, #425, #477, #480) is the member’s side of community-git-namespaces. It reads git-ns/view and shows the community’s repositories with the member’s strongest right on each (implied rights included), where they may create one, the people on a repository, creation progress and drift the bridge reported — which an owner or namespace admin can revert (v) or adopt (o) from here. Administrators see a banner for every unratified break-glass right. The new-repository form can name co-owners. Since 0.5.0 each repository also shows what is on this machine: c clones into <workspace>/<forge>/<owner>/<repo>, e/E make a checkout sign or stop, u adopts an existing checkout, s sets did-git-sign up for the persona (or repairs it), S removes it, f chooses the forge account (a gh login, an SSH key, or the git default) a repository clones and pushes with — F forks when that account cannot push — and w picks the workspace and protocol (following gh’s own git_protocol by default).

Non-TUI surface: openvtc setup (its start page now lists the steps it runs; running the pnm command is its own step; the setup DID is new each run and the grant command carries --admin-handoff); openvtc health [--vtc <did>] [--json] [--recoverable] [--allow-private-probes] — reports local storage first (which credential store, whether this profile’s secret is there), then the DID this install authenticates to the VTA as (with the pnm acl commands ready to run), whether this install can read its own faces, each community’s TSP relationship state, and graded transport probes that dial public HTTPS only and never follow a redirect; openvtc theme list|set|import|export (see Configuration below); --monochrome (0.5.0) draws without color, as NO_COLOR does; --unlock-code-file <PATH|-> replaces the deprecated --unlock-code in argv. A dev-overrides cargo feature is the only build that honors OPENVTC_VTA_URL / OPENVTC_VTA_DID / OPENVTC_MEDIATOR_DID; release builds ignore them and say so. The TUI starts on Windows again since #419.

openvtc-core

Shared library (now publish = true): config (v2 Account / PersonaRecord / CommunityRecord / IdentityRegistry, each with a forward-compatible extra catch-all and a LoadIntegrity report), BIP-32 key derivation, relationship state machines, messaging.rs (a pure protocol state machine), didcomm.rs (the transport, built on affinidi-messaging-delivery, with stored-mail pickup), tsp.rs + tsp_store.rs (TSP Rev 3 send path and the durable §7.2.2 relationship store), trust_task_doc.rs (the one place a Trust Task document is built and signed — build_signed_value / build_signed_value_as), proof_check.rs + issued_credential.rs + status_list.rs (the verifier every inbound credential and signed reply passes through: signer-DID binding, proof purpose, proof sets including ML-DSA-44, Bitstring Status Lists read and verified locally), dtg.rs (the client’s side of DTG Credentials v1: issuer scopes, conformance parsing, role VACs, predicate-based statement display), agent_name.rs, capabilities.rs, presentation.rs (DCQL → consent summary → vp_token), join.rs, members.rs, renewal.rs, vrc.rs, personhood.rs, operational.rs, vetting/ (tickets, applicant, vetter, book, wire, registry, protocol — the 0.3/0.2 join-requests negotiation — and hidden/, the client side of hidden vetting), persona/ (pool, profile, lifecycle, binding, world, family, claim types, disclosure, contact, correlation), git_ns.rs (the eight member-facing git-ns/* Trust Tasks in generated types), git_workspace.rs + git_signing.rs + forge_credential.rs (checkouts, did-git-sign identities provisioned without init, forge accounts), rebuild.rs / rebuild_apply.rs / context_probe.rs / credential_sync.rs (recovery from the VTA), devices.rs, community_access.rs, vta_receive.rs (the admin session’s reply-health watchdog), secure_store/ + diagnostics.rs, health.rs + net_guard.rs, process_lock.rs, and OpenPGP card support. MSRV 1.95, edition 2024.

openvtc-vetting-pcs

New in the Eucalyptus window (#396, 2026-09-29): “hidden-vetter admission (ZKP) for OpenVTC — the Predicate Credential System behind the vetting flow”. It wraps the published predicate-credential-system crate (OpenVTC’s fork of etairi/predicate-credential-system, with the one patch — caller-supplied application contexts — that the integration needed; the vendored copy and its PROVENANCE.md are gone) on arkworks BLS12-381, and holds the applicant engine (applicant.rs: a fresh key per application, the proof), the vetter engine (vetter.rs, token.rs: the blind class credential and the attestation-token wallet), the issuer and verifier halves a community runs (issuer.rs, verifier.rs, vtc.rs — the local stand-in the tests use; the real one is in vtc-service behind --features vetting-pcs), snapshot.rs (what the client persists between a request and its answer), meta.rs/scheme.rs (the public statement metadata that rides beside an attestation, in JCS bytes so both sides build the same context) and wire.rs (extensions.hiddenVetting, the JSON both repos must agree on). Counting is vta-sdk’s own vetting::requirements::evaluate, unchanged, with the vetter’s tag where its DID used to be — so join.rego needs no change. openvtc-core depends on it, so it publishes first. See Recent Development for how it works.

Crates that have left the workspace

  • did-git-sign — the git/SSH signing helper that uses the VTA as a signing oracle was developed and dogfooded here (auto-configured by the setup wizard since v0.1.6; per-repo persona selection since T8) and then extracted to its own repo, Verifiable Git Infrastructure (VGI), in July 2026 (#159). OpenVTC consumes the published crate as a library — 0.4.5 at Cypress, 0.4.6 at Dogwood (via a git pin on VGI while VGI caught up with vta-sdk), 0.4.12 at Eucalyptus-RC-0, 0.18.2 at Eucalyptus (through 0.4.13, 0.4.14, 0.5.2, 0.8.1, 0.10, 0.11, 0.15.1 and 0.18.1 in between, almost every one a “so OpenVTC can move” release on VGI’s side) — and pins VGI’s verify-trust GitHub Action by commit SHA for its own CI. The standing obligation recorded in Cargo.toml — VGI’s vta-sdk line must track OpenVTC’s or two vta-sdk copies land in the binary — was settled rather than patched on 2026-09-13 (#321) and then exercised a dozen more times in the Eucalyptus window, since vta-sdk moved from 0.42 to 0.63 in three weeks; cargo tree -d reporting one copy of every vta / trust-tasks / affinidi crate is now a line in most dependency PRs. The old open item — installing did-git-sign from the setup wizard had been unreachable since the R-A-5 split — was closed by a different door: the Repos panel’s s (#425, 0.5.0) sets did-git-sign up for a persona without did-git-sign init or a pnm grant (see Recent Development). OpenVTC also now depends on vgi-core (0.18.1) for the SSHSIG signed-data encoding it uses to prove a signing grant (#475).
  • openvtc-service (background DIDComm daemon) and robotic-maintainers (auto-accept test service) were removed in v0.2.1 (#63); the daemon’s role is covered by the TUI’s own messaging runtime, and the test-fixture role moved to the in-tree mediator harness. openvtc-cli (legacy prompt-driven binary) was deleted in v0.2.0.

Dependency snapshot

OpenVTC functions as the reference client for the VTI stack, so its dependency lines are the clearest record of what each coordinated tag actually means. All values are the resolved Cargo.lock versions (the manifest floors are the same or one patch lower):

CrateCypress 08-17VTI-Dogwood-RC-1 08-29VTI-Dogwood 08-30VTI-Dogwood-R1 08-31VTI-Eucalyptus-RC-0 09-17VTI-Eucalyptus 10-07
workspace (openvtc, openvtc-core, -vetting-pcs)0.3.10.3.10.3.10.3.10.3.10.5.0 (0.4.0 on 10-04)
vta-sdk0.25.00.32.10.32.20.32.20.42.10.63.3
trust-tasks-rs / -capability-client0.9 / 0.80.17 / 0.170.17 / 0.170.17 / 0.170.21.3 / 0.21.00.27.7 (git-ns feature) / 0.27.4
dtg-credentials (dtg-credentials)0.2.00.3.00.5.00.5.00.9.1 (WD-02)0.13.0 (v1)
predicate-credential-system—————0.1.0 (new)
affinidi-tdk (affinidi-tdk)0.80.100.100.100.160.23
affinidi-messaging-sdk / -delivery / -core0.19.4 / 0.1.12 / 0.1.60.21 / 0.1.14 / 0.1.6samesame0.26.7 / 0.1.15 / 0.1.80.33.2 / 0.1.20 / 0.1.11
affinidi-tsp (transitive)0.1.140.1.140.1.140.1.140.2.1 (Rev 3)0.2.2
affinidi-did-resolver-cache-sdk / didwebvh-rs0.8.190.8.340.8.340.8.340.8.37 / 0.70.8.41 / 0.8
agent-names0.1.20.1.30.1.30.1.30.1.30.1.3
did-git-sign / vgi-core (verifiable-git-infrastructure)0.4.50.4.6 (git pin)0.4.6 (git pin)0.4.6 (git pin)0.4.120.18.2 / 0.18.1
vta-service / affinidi-messaging-test-mediator (dev)0.17 / 0.20.23 / 0.40.23 / 0.40.23 / 0.40.32 / 0.90.54 / 0.18
[patch.crates-io] entries0VGI git pinVGI git pinVGI git pin00

vta-sdk features at Eucalyptus: session, client, keyring, didcomm, provision-client, vetting, tsp, vp. Three edges are explicitly coupled to vta-sdk in the manifest’s comments — trust-tasks-rs (re-exported types cannot unify across two copies), since 0.42 dtg-credentials (the sdk itself depends on it), and did-git-sign (the one external crate that also depends on vta-sdk, so it must publish on the new line before OpenVTC can take it) — and cargo tree -d on each is the check. The constraint recorded at RC-0 still holds: vta-sdk ≥ 0.35 refuses an unsigned trust-task reply, and from 0.4.0 the client refuses unsigned requests and replies “taken on the sender’s word” too, so the VTA and VTC it talks to must be on the Eucalyptus line.

Identity Model

OpenVTC’s identity model has three layers, the third added in September 2026:

  • Persona / Membership DIDs — your public identities, one per community membership, created as did:webvh hosted on the domain of your choice (via the VTA’s configured DID host), with a server-allocated mnemonic path or one you type (#324), and optionally claimed by an agent name in alsoKnownAs. Since 301 each persona’s keys and DID are minted in a VTA sub-context — by default one per community, so communities no longer share one key hierarchy and are isolated in more than name. A membership row says when the same persona is shown to other communities, because that linkage is the one thing a holder cannot work out from a single row.
  • Relationship DIDs (R-DIDs) — private did:peer identifiers, one per relationship. Since Dogwood (#254/#255) pairwise is the default, not the opt-in, and the VRC is issued under the R-DID — the durable artifact both parties keep is the worst place to leak a persona. The three handshake messages still route persona-to-persona (the mediator has to route them before a pairwise channel exists); full pairwise operation is a protocol change tracked as VTI #1054. A community may declare relationshipIdentifierDefault: attributed, which seeds the form but never gates it (#335).
  • Facts, faces, and what a persona wears — the VTA’s persona/* family, one layer above the DID: an attribute is a fact, a profile is a face (a named subset of facts), and a persona wears a face in a community (persona/binding). Since #362 a binding also carries the label a community may call the face — the holder’s own name for a face is given only to the holder. Since #367 a face can be composed for one community where it is asked for, retired (kept with its history) and reinstated, and its timeline read — made, worn, taken off, told whom which types, values changed; deleting a face says plainly that it “does not un-tell” the parties it disclosed to. A facet became a world (#378): a named arrangement of faces, offered — not asked for — once a holder has four. The vocabulary is fixed across the console, pnm, the mobile agent and this TUI by design-docs/persona-vocabulary.md; the spec’s words stay on the wire and off the screen. Reaching this layer needs the persona-holder capability (pnm … --admin-holder at setup), which grants authority over your own identity without making OpenVTC an administrator of every context (VTI #1286).

A name is only ever displayed if it forward-resolves and round-trips back to the labeled DID (user alias → verified agent name → truncated DID); the DID, never the name, is what gets persisted. Sensitive facts are masked by the claim-type registry the agent serves (persona/claim-types/list) and fetched one at a time on s — bulk reads never carry a card number into the process (#340); since #378 a single fact is read by id (persona/attribute/get) rather than by listing and filtering, so the agent’s audit trail records a decision about one value, not a sweep of the pool.

Two keys, two jobs (#386, #390, VTI-KEY-022). Each persona DID publishes an assertionMethod key (#key-1) and an authentication key (#key-2). Since 0.4.0 every Trust Task request leaves signed: claims to be held to later — a join submit, a self-removal, the reciprocal member VMC, a VRC, a Vetting Statement, a git-ns grant — under assertionMethod; acts on one’s own standing — the personhood challenge and assertion, a capability toggle, a renewal, a vetter profile — under authentication. The VTC’s strict proof-purpose rule refuses a proof by a key not listed under the purpose it claims, so getting this wrong is a refusal, not a warning.

TSP Rev 3 relationships are a fourth, separate keyspace (Eucalyptus-RC-0, #333): the §7.2.2 relationship this account has formed with each community VTC, without which the VTC silently drops application traffic. They are persisted in ProtectedConfig so a restart does not forfeit them.

Configuration

OpenVTC uses a three-tier configuration system:

TierStorageContents
PublicConfigJSON on diskNon-sensitive settings
SecuredConfigOS keyring/keychainCryptographic secrets
ProtectedConfigEncrypted inside PublicConfigSensitive data encrypted with unlock code

Protection modes:

  • Unlock code — encrypt with a user-provided passphrase (HKDF-SHA256 + random nonce)
  • Plaintext — no encryption (development only)
  • Hardware token — encrypt via OpenPGP card (Nitrokey/YubiKey)

Multiple profiles are supported via the OPENVTC_CONFIG_PROFILE environment variable or -p flag.

Where the secrets live (Dogwood, #246). The OS credential store is now chosen by vta_sdk::keyring_init::install_default_store — the same call pnm-cli makes — so openvtc and pnm keep secrets in the same place: Apple Keychain, Windows Credential Manager, or DBus Secret Service. On Linux OpenVTC had been the only tool using the kernel keyutils keyring, which is RAM-only and had been quietly destroying accounts on reboot. If the store cannot be opened OpenVTC fails closed rather than downgrading; headless machines opt into an encrypted file store with OPENVTC_SECURE_STORE=file (~/.config/openvtc/secrets/, mode 0600, refuses to hold an unencrypted profile). The secured envelope is written as one line, because gnome-keyring corrupted its whole collection on a pretty-printed one (#269). ProtectedConfig is now encrypted under a random per-profile key held in SecuredConfig, not under a derivation of the admin credential (D12) — so the credential can rotate or be re-issued to a recovering install without making local state undecryptable. Startup failures each get their own diagnosis (~/.config/openvtc/last-startup-failure.txt).

The VTA as system of record. docs/design/vta-authoritative-state.md (DRAFT v6) sets the direction: keys, identities and credentials live at the VTA; local config is a cache that can be rebuilt. The rebuild path (#248–#251) reconstructs personas (re-establishing DID ↔ key correspondence with the VTA’s own select_secret_kid rule) and memberships (from VMCs now stored in the VTA credential vault via vault/credentials/receive), and setup offers it when a context is already occupied. What is still missing is an application-state store on the VTA (ask E2, blocking) for the metadata that is neither key nor credential.

Themes (tui.toml). Since 300 the TUI is drawn in any theme: built-in (including High Contrast and Colourblind Safe pairs measured against WCAG), your own under ~/.config/openvtc/themes, Omarchy’s (followed live), or imported from base16/base24, Alacritty, Kitty, Ghostty, or any Neovim colorscheme. auto — the default since 0.5.0 (#457) — follows the terminal background (OSC 11 → COLORFGBG → dark); a terminal that does not declare 24-bit color in COLORTERM is drawn with the nearest of the 256 indexed colors, which is what made macOS Terminal.app readable; NO_COLOR and --monochrome draw without color, keeping roles apart with bold, underline and reverse (#460); OPENVTC_THEME overrides for one session; openvtc theme export writes a theme back out for other tools. The choice belongs to you rather than to a profile (a per-profile remembered choice was scoped in #336).

No built-in mediator or organization. Until 0.5.0 setup stamped a fixed organization DID on fpp.storm.ws into every account and a persona minted against a VTA that advertised no DIDComm mediator fell back to a built-in public one on the same host. Both are gone (#459): the mediator comes from your VTA’s DID document, and create-persona and the join flow refuse — before minting anything — when the VTA advertises none.

Repositories (git_workspace, docs/forge-accounts.md). The Repos panel keeps checkouts under a workspace root (<root>/<forge-host>/<owner>/<repo>, or a path you gave), stores only an explicit protocol choice (clone_protocol) and, per community-and-forge or per repository, a reference to the forge account git should use — a gh login or an SSH key path, never a token. did-git-sign identities OpenVTC provisions are stored as did-git-sign’s own named profiles, in the same OS credential store OpenVTC uses (which is why VGI 0.18.2 had to switch did-git-sign’s Linux store from the kernel keyring to Secret Service, #148).

Recent Development

The focus has moved from security correctness (v0.1.x), through feature completeness (v0.2.0), through the multi-community pivot (Banyan, June 2026) and making that client reliable (v0.3.0 / Cypress, August), through making the VTA the system of record and building the identity pane (VTI-Dogwood, VTI-Eucalyptus-RC-0, September), to shipping vetted admission — the thing the project set out to do for the Linux kernel maintainers — in two releases cut for a date: 0.4.0 (2026-10-04) and 0.5.0 (10-05/06), both inside VTI-Eucalyptus (2026-10-07) (coordinated-releases). Along the way the client picked up a zero-knowledge mode for vetting, DTG Credentials v1, a proof-first security pass, and the member’s side of community-governed git repositories.

Eucalyptus (2026-10-07) — 0.4.0 and 0.5.0: vetted admission shipped, hidden vetters proven in zero knowledge, DTG Credentials v1, and the community’s repositories

VTI-Eucalyptus-RC-0..VTI-Eucalyptus: 145 commits, 195 files, +79.8k / −4.9k; the 2026-09-18 → 10-07 window is 133 commits and PRs #346–#481. The vetting design doc had set V0 for 5 October 2026 in Prague, ahead of the Kernel Maintainer Summit; 0.4.0 was cut on the 4th, 0.5.0 on the 5th (twenty-six PRs merged that day alone), and the project was presented at the Linux Plumbers Conference on 2026-10-07, the day the tag was laid. Two things frame the window beyond the deadline: the CHANGELOG calls 0.4.0 “the vetted-admission release” and 0.5.0 “the guided-vetting release” — first make it work, then make a person understand where they are in it; and vta-sdk moved from 0.42 to 0.63 underneath, so almost every feature PR carries a dependency step and a cargo tree -d line.

The join as a guided path, and vetting as a journey (#344–#360, #412, #423, #432–#441, #444–#451; 09-18 → 10-05)

  • The join page reorganized (09-18/19): requirements read from the endpoint the community publishes (#344); a pasted DID read as a DID, not a broken invitation (#347); a persona (#348) or a face (#358) made without leaving the flow; the vetting page one list of choices with DIDs you can read (#353); the two ways of holding no invitation told apart (#352); a route whose first step the join can supply is not blocked (#351); apply as a second persona (#359); abandon an application (#355); answer what a community asks about you through a disclosure (#365). The join speaks join-requests 0.3 and falls back to 0.2 (#412) — a community on Keyring VTI-13 serves only 0.3 — remembering per community which it got.
  • Journeys (0.5.0, #438). An application, or a desk request, opens as a full-width page with a step strip (done / yours now / waiting on someone else / to come) and a “What’s happening” box that says why — why a face, why the match code is read aloud, what the community learns. Only the keys of steps already reached work, and the journey moves on by itself when a session opens or a statement arrives. Around it: review before sending (#440) — the DID the community admits, the invitation and its linkage proof, each named statement (who vouched, how, what they checked) or a PCS proof that names no vetter, what the face sends, how the community decides; trust-registry consent (#439) — openvtc had hard-coded registryConsent: false, so nobody who joined through it could ever be published; the box starts unticked and only Space ticks it; a vetter’s optional decline reason (#441); held statements get their own Credentials tab (#423); a statement counts only for the persona it vetted (#445); a finished request leaves the desk keeping nothing about the person (#444); the community’s current requirements are read on every join, and named vetting is marked as a disclosure — an orange ⚠ VETTERS NAMED badge with “the community will see which vetters vouched for you” — while PCS ZKP gets a green ✓ (#446).
  • Badges and plain words (#433): PQC-SIGNED where a credential or community signs with ML-DSA as well as Ed25519, PCS ZKP where a community proves vetting in zero knowledge; the credentials list says what each credential is.

Hidden vetters: the Predicate Credential System (#396, #433–#434, #442–#443, #449, #452–#456, #461–#468, #471–#472; 09-22 → 10-06)

The V0 spec listed “k-of-n proofs over hidden vetters” as V2. It shipped in V0’s own window. docs/design/vetting-hidden-vetters-pcs.md (2026-09-22, with a same-day second-pass review and dated addenda through October) sets the goal: Bob proves to a VTC that k distinct, currently eligible vetters of that community vetted him, without the VTC — or anyone — learning which. The construction is the Predicate Credential System (PCS, etairi/predicate-credential-system, BLS12-381): a helper issues blind class credentials; a credentialed user attests for another’s identifier; the applicant proves it holds k attestations from pairwise-distinct credentialed users, none of whom is itself. Each attestation carries a tag that is deterministic per (vetter, applicant) — so the same vetter twice is caught — but unlinkable across applicants and to the vetter, even for the helper. An attestation is 240 bytes and ~3 ms; a proof at k = 5 is 1.4 kB and ~12 ms to make or check. It is a research artifact, unaudited; OpenVTC forked it, added the one change the integration needed (caller-supplied application contexts), and published it to crates.io so the vendored copy could go (#396, 09-29).

What makes it fit is the role mapping: vtc-service is the helper, with keys derived from the community’s credential signer rather than stored; the vetter’s class credential is issued when the vetter role is granted; and the VTC emits the same VettingFacts shape with tags where issuer DIDs used to be, so counting, method floors, independence caps and join.rego are untouched. Accountability is kept by attestation tokens on an unconditional drip — a vetter draws on a schedule whether or not they vetted anyone, “because a fetch that happened only when someone was busy would announce that they were busy” — with ticks that are windows of time from a label’s start (#452, trust-tasks #734) and an event mode (e) for conferences. The ceremony up to the human check is unchanged; only what the vetter hands back, and what the VTC verifies, differ. The community decides the mode in its manifest’s vetting.ext; the client needs no flag, and vtc-service must be built --features vetting-pcs. docs/design/hidden-vetting-live-run.md walks a real community through it: register a criterion, POST /v1/vetting/hidden, name vetters, each vetter’s client enrolls and draws (m), the applicant mints a key for this application and no other, a ticket, a session, a card, one proof over three attestations — and afterwards the community can show “three distinct vetters, their methods, their claims — and three tags where three DIDs would be”.

The week of 0.5.0 was the first time it met real clocks and real mediators, and the fixes are the story: the join fetches its own challenge (vtc/vetting/pcs-challenge/0.1, 15 s, Esc to leave; #442, #443) and a join refused over its challenge ends with the reason instead of sitting Pending (#454); a vetter learns that a community turned PCS on — the re-read and enrollment used to run only on m from an application, which a vetter-only member never has — and never signs a named statement for it (#449); the four vtc/vetting/...#response types were never claimed by the inbound router, so every enrollment the community issued was dropped and every retry refused alreadyEnrolled (#456); a token balance on the desk, and no ticket you cannot honor (#456); tickets follow the live mode, k gets tokens in one key, the enrollment blinding is saved before the request so an answer survives a restart (#462); a lost enrollment is asked for once more (#466, VTI #1972); the join page shows a proof as ready rather than “0 statements” (#467); when a community changes its published parameters the criterion digest moves and earlier attestations stop counting — the checklist now says so and the join stops before submitting (#468); draws follow the community’s current rate (#468); a vetter follows the request — a PCS attestation for one that carries an identifier, a named statement otherwise — and a new application defaults to hidden vetting where the community offers it (#471); the mode is read once the listener is up, not minutes later (#472).

DTG Credentials v1 and the proof-first security pass (#371–#372, #380–#392, #397–#398, #403, #406–#408, #411; 09-23 → 10-02)

  • Sign what the specs say to sign (#371). join-requests/{submit,status}, members/{self-remove,vmc} and members/personhood/assert went out unsigned, relying on the authcrypt or TSP sender. Their specs declare proof REQUIRED, and VTI #1659 had had to add require_declared_proof = false to the VTC because of this client. “Transport attribution is not the same claim: an authcrypt sender says who handed the document over, the proof says who wrote it.” One trust_task_doc::build_signed_value now signs every one of them.
  • Keyring VTI-42 (#372). The Berkman Klein Center’s Keyring phone wallet, interoperating against the same VTCs, found that OpenVTC still typed several messages with the task URI instead of the DIDComm binding envelope; VTI #1687 then removed the VTC’s task-typed arms, and this PR moves the manifest request, the vetter directory/profile/resend, revoke-statement and both personhood legs into the envelope, and opens the envelope before routing inbound.
  • Decisions rest on a proof, not the sender (#380–#392). A credential delivered over credential-exchange/issue was stored after checking only that its issuer matched the DIDComm sender; now its Data Integrity proofs (eddsa-jcs-2022 and mldsa44-jcs-2024) must verify with an assertionMethod key of the issuer’s own DID, its validity window must hold and its status list must be reachable and verifiable — fail closed, with the list read and checked locally (status_list.rs), proof sets included. An approved status or allow verdict no longer activates a membership: the join is acknowledged, and Active — with the reciprocal VMC sent — only when the community’s credential arrives and verifies. Proofs are required on relationship DIDs, removal notices and community answers (#381); capability and git-ns replies, invitations and join failures are authenticated (#383); pushed credential-exchange documents are read and request-vmc answered only when signed (#392); every Trust Task request is signed as the persona (#386), with the authentication key for operational acts (#390, trust-tasks 0.23 making personhood/challenge, vetters/profile and vetters/resend proof-REQUIRED). Network-bound checks run off the UI loop (#387).
  • DTG Credentials v1 (#397, 10-01, breaking; dtg-credentials 0.12 → 0.13, vta-sdk 0.57). Every DTG credential the client issues, receives, verifies or stores carries the v1 context https://registry.trustoverip.org/dtg/context/v1 (the firstperson.network context refused, no alias), a top-level issuerScope (directed for the persona DID a membership names, pairwise for a relationship DID, public for a VIC or the community’s own VMC) and exactly one concrete type. The vetter’s Vetting Statement is a StatementCredential under the registry predicate vetted/1, citing the signed vetting/session document by taskContext and taskDigestMultibase; witness statements are witnessed/1; the vetter role is an Authority Credential (role:vetter) rather than a CommunityRole. Credentials stored in a pre-v1 shape are set aside on load and listed as retired — hence membership renewal (vtc/members/renew/0.1, R, #398), which asks the community to re-issue the VMC and role credential and verifies both before replacing the old ones. A community may issue vetted/1 for itself (vta-sdk 0.59, #403), and OpenVTC keeps that and offers it as personhood evidence (#408). See dtg-credential-spec and dtg-credentials.
  • A join that was approved but whose credential never arrived can be rescued by hand (R, #406) and, since #411, asks automatically: the status poll carries resendCredentials once the community has reported credentialsDelivered: false (trust-tasks #709, VTI #1900), at most six times per record per process.

Faces, worlds, and the rest of persona/* (#361–#370, #378; 09-20 → 09-23)

What a community may call a face (the binding label, #362, vta-sdk 0.45 — breaking on the stored records, and binding/set now reads the label back before replacing it, so changing a face from OpenVTC no longer silently strips the name a community was given); compose a face for a community where it is asked for, retire and reinstate faces, read a face’s reach and history, and hear in the delete prompt what deleting cannot undo (#367, vta-sdk 0.48); values from a community’s own faces reusable (#368); People (persona/contact/* — what others have told the holder, filed against the persona that received it), Formats (persona/renderers/list), who a community knows you as (persona/binding/list, warning when it holds two), purge earlier versions of a fact, and faces made inside a community (#370); one attribute read by id, and a facet renamed a world (#378, vta-sdk 0.51 — which first needed did-git-sign 0.4.13, the VGI edge again).

The community’s repositories: the Repos panel (#377, #379, #382–#384, #391, #425, #474–#480; 09-24 → 10-06)

  • The member side of git namespaces (#377, 09-24; trust-tasks 0.22.3 with the git-ns feature). openvtc_core::git_ns sends the eight member-facing git-ns/* Trust Tasks — view, repo/create, right/grant, right/revoke, repo/transfer, repo/archive, account/link, account/link-status — in the generated types (so an unqualified resource or a DID without a method fails before it is sent, naming the field), each signed by the persona and carried in the binding envelope. A trust-task-error becomes a Refusal whose explain() gives each declared code the fix it implies: lastOwner says to add another owner first, policyDenied keeps the VTC’s reason. The panel shows the member’s repositories with their strongest right, where they may create, the people on a repository, creation progress and drift. Drift can be answered from the panel (#379, git-ns/drift/resolve): v reverts to what the rights call for, o adopts a forge-side role as the right it projects, each armed and classed as the revocation or grant it amounts to. Break-glass rights are shown to every administrator they concern, with the cnm commands to ratify or revoke (#384, git-ns/view 0.4). repo/create 0.3 closed a hole (#391): git.ns.admin implies git.repo.create, and 0.2 made every creator the owner — so a namespace admin could make themselves owner and forge admin of any repository, alone; now the requester becomes owner only when their create right is an explicit grant, and a refused elevated self-grant (git-ns:selfGrantNotAllowed) is explained as separation of duties. See community-git-namespaces and the VGI page for the server and bridge sides.
  • Checkouts and signing (0.5.0, #425, 10-04). git_workspace keeps a checkout at <root>/<forge>/<owner>/<repo>, inspects what git reports (origin, branch, ahead/behind, the signing settings in effect, whether HEAD is signed and carries a Signed-by-DID: claim) and clones without ever prompting. git_signing sets did-git-sign up for a persona without did-git-sign init or a pnm grant: openvtc mints a did:key, grants it — through its own admin session — admin of the persona’s own sub-context narrowed to one capability, proves the grant before storing anything, revokes it if the proof fails, stores the identity as a named did-git-sign profile with the library’s own calls, and runs did-git-sign enable --profile in each checkout. A persona whose keys live in the account’s top context is refused, since a grant there would reach every persona. The key stays in the VTA (#475, 10-06): the capability is now sign-sshsig when the VTA serves keys/sign-sshsig (asked with trust-task-discovery) and key-export only for an older one; the proof asks the VTA for a real SSHSIG signature and checks it against the key the persona’s DID document publishes — the key is never fetched. The e2e runs against vta-service 0.54, the first mock that serves the task (#478). Forge accounts (f, #477): a gh login, an SSH key or the git default per repository or per community-and-forge; a gh account also authors commits as itself, the screen warns when it is not the account linked to your membership (“the community closes its pull requests”) or cannot push, and F forks. A clone follows gh’s git_protocol by default and warns when an HTTPS checkout has no credential helper (#480). A TSP-joined member’s Repos view works: git-ns requests go on the membership’s transport (#474) and replies are admitted at the inbound gate (#476 — over TSP the envelope is opened before the gate, so git-ns/view/0.4#response had been dropped with a debug line; anything no handler claims now logs at WARN).

Transport, setup and platform (#346, #369, #376, #385, #393–#395, #399–#402, #405, #409–#410, #413–#415, #419–#422, #457–#460, #465, #469–#470; 09-18 → 10-06)

  • TSP end to end. The peer’s mediator is recorded before the Rev 3 invite so it is routed, not sent direct (#405); a membership joined over TSP is spoken to over TSP afterwards — the reciprocal VMC, renewal, personhood had gone over DIDComm whatever the join used, so a TSP-only persona’s acknowledgement never arrived and the community re-sent until both sides’ limits ran out (#413); leave over TSP (#414); and VTI-61 (#415): TDK::new built tdk.atm with the SDK’s default in-memory relationship store, so an invite was recorded Pending there while inbound frames were unsealed on the shared store that held None; the community’s Accept was refused and its replies discarded as “no relationship”, and a persona on another mediator never received its credential or vetter grant. TspStoreHandle::adopt_into rebuilds tdk.atm on the shared store before the listeners start.
  • Setup speaks the VTA’s current grammar: --admin-handoff on the pnm command and a /v1 VTCRest endpoint reached (#399); ACL instructions that cover an existing context and a retry (#401); a start page that lists the steps, and a setup DID that is new each run (#402); the PNM step its own page, with a rejected DID leading back to it (#458); the setup ask is a holder client’s (vta-sdk 0.61, #407).
  • Robustness: a failed listener rebuild is retried (#421); an admin session whose replies stop arriving — two sessions sat deaf for an hour on 10-05 while every reply queued at the mediator — is noticed from consecutive reply timeouts and rebuilt on a capped, jittered backoff, with a Replies row on the VTA panel (#469); affinidi-messaging-sdk 0.32.1 stops losing a DIDComm reply that arrives after a wait expires (3–5 % of single messages, #410) and 0.33.2 / delivery 0.1.20 recover a receive side that stays up but delivers nothing (#470).
  • Platform: the TUI starts on Windows — crossterm’s DisableMouseCapture was sent though capture was never enabled, and on Windows that fails “Initial console modes not set” (#419); readable in Terminal.app, default theme auto, --monochrome (#457, #460); no built-in storm.ws organization or mediator (#459); the workspace publishable (#422); affinidi-did-resolver-cache-sdk 0.8.39, after which a deactivated did:webvh no longer resolves (#400).
  • The dependency climb: vta-sdk 0.42.1 → 0.43.1 (#346) → 0.45 (#362) → 0.48 (#366) → 0.49 (#369) → 0.51 (#378) → 0.56 (#393) → 0.57 (#397) → 0.59 (#403) → 0.61 (#407) → 0.62 (#411) → 0.63 (#425) → 0.63.3 (#465); trust-tasks 0.21.3 → 0.27.7; affinidi-tdk 0.16 → 0.23; messaging-sdk 0.26.7 → 0.33.2; dtg-credentials 0.9.1 → 0.13; did-git-sign 0.4.12 → 0.18.2 — each VGI step being what let the next vta-sdk step resolve to one copy. One [patch.crates-io] appeared, for git-ns/repo/create 0.3 ahead of its trust-tasks release, and was reverted in the same PR (#391).

Still open at Eucalyptus: changing registry consent after joining (the community has no member-signed task for it); vta-sdk 0.64’s own receive_health once did-git-sign releases on that line (#469 stands in for it); persona key rotation; consuming vtc-client for the join ceremony; on the hidden-vetting side, approving an event is a criterion re-post rather than a console action, and the class label is rotated by hand. The design doc’s §14.2 V1 list — vetter directory at scale, VTA-side tickets and gate, push notifications, lineage and cascade review, the PGP bridge — is what “operable at kernel scale” still means.

Dogwood (2026-08-30) → Dogwood-R1 (08-31) → Eucalyptus-RC-0 (2026-09-17): the VTA as system of record, vetted admission, and the identity pane

The workspace stays at 0.3.1 throughout — no vX.Y.Z tag, no GitHub Release for Dogwood, ~960 lines of [Unreleased] CHANGELOG. Two design specs frame the window. docs/design/vta-authoritative-state.md (DRAFT v6) starts from “My laptop died. I install OpenVTC on a new machine, point it at my VTA and my Trust Context, and it offers to recover” — and finds that the admin credential was doing two irreconcilable jobs, that concurrent instances break the messaging layer today, and that the VTA needs an application-state store (E2, blocking, still open). docs/design/vetting-process.md (DRAFT v3, V0 target 5 Oct 2026) replaces the Linux kernel maintainers’ PGP web of trust with peer identity vetting: an applicant builds a signed Vetting Card from a persona face, presents it to several vetters who check it against a passport in person or on video and issue Vetting Statements, and submits them to the VTC, which admits under policy. Most of that protocol lands in the ToIP Trust Task specs and VTI; OpenVTC is where both halves — applicant and vetter — become a screen.

Dogwood — recoverable installs, honest storage, and the membership pair finally landing (#238–#272)

Cypress..VTI-Dogwood: 41 commits, 80 files, +18.6k / −2.6k. A “silent” release by design: almost everything here is a bug that had been making the client lie, and the fix is that it now tells the truth.

  • Storage that survives a reboot (#246). On Linux OpenVTC had registered the kernel keyutils keyring — documented as RAM-only — as the sole home of a profile’s seed and credential bundle; a reboot, a logout, or three days of the persistent keyring’s default expiry destroyed the account, surfacing as Couldn't find openvtc secured configuration. Store selection now delegates to the SDK’s install_default_store, fails closed instead of downgrading, and offers OPENVTC_SECURE_STORE=file for headless machines. In the same PR: Config::save writes secrets before the config file so an interrupted save leaves a persona absent rather than half-written; a half-written persona no longer fails the whole load (LoadIntegrity, acknowledged on the startup screen, “nothing has been deleted”); every startup failure gets its own diagnosis instead of “check your network”; and openvtc health reports local storage first, even when no account loads.
  • Break the credential ⇄ encryption coupling (D12). ProtectedConfig was keyed on the admin credential’s private key — an authorization grant meant to rotate, doing duty as a data-at-rest key that must not. The profile now carries its own random key; load tries three keys newest-first and re-keys on the next save, crash-safely. Config records gain a flattened extra so an older build round-trips a newer one’s fields (D19).
  • Setup looks before it writes; recovery is a branch in setup (#247–#251). Pointing a fresh install at an occupied Trust Context used to mint a second set of personas beside the first. Setup now probes (personas, sub-contexts — counted, never gestured at; “could not tell” kept distinct from “empty”), offers use a different context first, continue on a deliberate C, and never offers delete. openvtc_core::rebuild reconstructs an account from what the VTA holds: DID ↔ key correspondence via the VTA’s own select_secret_kid; memberships rebuilt Active from their VMCs, each carrying its own proof. Running it live found that VMCs had never been stored at the VTA at all, that cred_vault_query({}) is refused by design (the vault is non-enumerable) and both callers had read the refusal as “zero”, and that query returns body-free descriptors — so a credential_sync on connect now pushes every membership credential through vault/credentials/receive (#251).
  • Privacy defaults flipped (#254, #255 — breaking on the wire). A new relationship mints a pairwise R-DID unless turned off; a accepts pairwise, p under the persona. Then the half that mattered: the VRC is issued under the R-DID, signed with its key — previously issuer = persona_did, so the durable, publishable credential led straight back to the persona. This needed VTI #1061 to stop pinning a published VRC’s issuer to the session DID; vet_vrc_issued gate 2 is now tighter and more private (issuer must be the sender’s DID in this relationship). A pasted VIC must now carry DTG’s common structure — the firstperson.network/credentials/dtg/v1 context and DTGCredential type — or it is refused at ingest (#256, breaking; the fixtures had encoded the implementation’s belief rather than the spec’s).
  • Personhood over Trust Tasks (#257). The VTC’s challenge/assert verbs were REST-only; a client that talks DIDComm/TSP and holds no bearer token could not take part. Both now ride the Trust Task document path. match_code derives eight Crockford-base32 characters (no I/L/O/U) from the challenge UUID — the thing two people in a room actually say to each other; 5CY1-GZEE is pinned on both sides so a drift reads as “software disagrees” rather than “wrong ceremony”. The challenge is written twice, to proof.challenge (what the spec names) and top-level nonce (what the Data Integrity signature actually covers).
  • The membership pair had never once landed (#259–#267). Every reciprocal VMC OpenVTC sent a community had been rejected — dtg-credentials 0.2 had no field for the W3C credential id the VTC keys on — and it was silent from both ends: the send reported success on a local Ok, and the rejection problem-report was threaded on a VMC delivery, so the join handler dropped it as “uncorrelated”. dtg-credentials 0.3 adds with_id() (#260); VRCs get a urn:uuid: id too (#265); an uncorrelated problem-report is now surfaced with its code (#267); and receiving the community’s VMC auto-issues ours back, closing the join request by its id (#266 — behavior change; a join used to sit Approved at the VTC forever). Around it: a removed listener’s socket was never closed, so re-adding a DID fought its own orphan indefinitely (#259); every launch after the first warned that the account was “open elsewhere” — the device listing could not recognize its own binding (#261, fixed by consumerDid); a renamed machine’s heartbeat carries its name (#264); the mediator’s scheduled token-refresh reconnect is one calm line instead of an alarm every twelve minutes (#262).
  • Setup could not link to a VTA at all — provision/integration moved to 0.3 upstream and only the SDK’s REST runner followed; setup prefers TSP, and the post-auth refusal was classified terminal so it never fell back. Floored at vta-sdk 0.32.1 (#268), then 0.32.2 (#271). The grant is acknowledged by digest and the credential we sent is kept (#272, dtg-credentials 0.5). Also: connection status seeded from the live listener rather than an edge, so a first-run join no longer shows “Connecting…” for the life of the process (#270); the R14 perf batch finished — every remaining network action (capabilities, leave, VMC issue, persona create, VRC request) off the state-handler loop (#239–#244), and the 1,600-line action match lifted into a testable runtime_actions::handle_action (#245).
  • Dependencies at Dogwood: trust-tasks 0.9 → 0.17, affinidi-tdk 0.8 → 0.10, vta-sdk 0.25 → 0.32.2, messaging-sdk 0.19.4 → 0.21, dtg-credentials 0.2 → 0.5, did-git-sign 0.4.6 via a git pin on VGI (#252, #253, #258, #263).

Dogwood-R1 (2026-08-31) — the re-cut

Two commits, seven files, +343 / −100. #273 is the one that mattered: a join to a community whose mediator is not ours failed at the first hop with TSP recipient is not local to this mediator — from our mediator, because the routed send had been passing the peer’s mediator as route[0] when only our own can unwrap the outer layer. Invisible in a single-mediator deployment; broken in the ordinary federated case. The route is now built outward, [ours, theirs, recipient]. #274 makes the mediator DID read-only in Settings on a profile with no persona and stops reporting saves that did not happen.

Toward Eucalyptus — My Identity, contexts, vetting, themes, hardening (#275–#333)

VTI-Dogwood-R1..VTI-Eucalyptus-RC-0: 56 commits, 116 files, +49.1k / −1.9k — the largest window in the project’s history.

  • The identity pane (#275–#290, #324, #340). It began by asking the agent which face a persona wears in each community (persona/binding/get, #275) — with “we could not ask” carried as a field, never as an absent Option that renders as “presents nothing” — and putting presents: work (3 claims) on every membership row (#276). Then My Identity (#277): personas, facts, faces, communities, disclosures in one pane, with values opt-in and fetched only on v, destructive prompts naming the real cascade, and credential-backed attributes shown but not retyped. Setup asks for the persona-holder grant (#279). The pane speaks the persona vocabulary (#280/#281), with a test that fails if any avoid-list word reaches the screen. Sensitive facts are masked by claim type (#282), first from a vendored table and then from the registry the agent serves, since a deployment may declare its own types (#290, with worlds and families). A face gets a cursor so a single claim can be read (#288). The DID path can be chosen (#324). include_sensitive becomes a real second escalation: bulk reads never fetch sensitive values; s fetches one (#340).
  • Each community in a VTA context of its own (#298, #301). OpenVTC had derived a sub-context id per membership but never created it, minting every persona in the top context — isolated in name only. The join now asks where the community lives (own sub-context by default), creates it, and mints the persona’s keys there; a finished membership can delete its context (with the VTA’s preview-delete cascade shown and DELETE typed); and per-community device access grants a second device’s did:key the least-privileged application role scoped to that context alone (1/7/30/90 days).
  • Vetted admission, V0 (#292–#297, #307, #319, #322, #325, #337). The design doc (#292); openvtc_core::vetting — tickets as the anti-spam gate (a wrong code gets no answer), applicant and vetter state, signed peer documents bound to the authenticated sender (#294); the Vetting page (#295, with a vetting community’s decisionSla replacing the 7-day pending timeout); the card read from the persona’s face through persona disclosure, so the community sees what the vetters checked (#296); vetters named by a revocable CommunityRole credential the community grants, presented as eligibilityVp on every acceptance (#297); the vetter directory, profile, QR tickets, grant status-list checks, resend and community branding (#307); the wire types swapped for the generated trust_tasks_rs::specs ones, which validate what the hand-written ones accepted (#319); the ceremony end to end over a real mediator (#322); the desk headed by the credential that makes you a vetter — the only view that keeps a lapsed grant, so a lapse is no longer invisible (#325).
  • Themes (#299, #300): seven color roles swapped per frame, so no panel changed; built-in, own, Omarchy, and imported themes; live switching within a second; auto light/dark; accessible themes measured for WCAG contrast; NO_COLOR; export. docs/themes.md.
  • Security (SEC-4045 and around it). openvtc health had dialed every transport URL in any DID document anyone could publish — any host, system proxy, ten redirects. It now probes public HTTPS only, no redirects, through a guarded DNS resolver (#304), and since #312 the resolver refuses did:webvh on localhost/private/link-local hosts too (--allow-private-probes opts both in together); regression gates prove no connection opens (#320). Release builds ignore OPENVTC_VTA_URL / _VTA_DID / _MEDIATOR_DID — an override set for one launch used to be written into the profile by the next save (#305; dev-overrides feature). The debug log is created 0600, never through a symlink, and defaults to a filter that does not include every dependency; --unlock-code-file keeps the passphrase out of argv; no factory-PIN literal in the binary (#309). CI actions pinned to commit SHAs, permissions: contents: read, Dependabot (#310). rustls 0.23.45 for RUSTSEC-2026-0285 (#323).
  • The dependency unwind (#286, #291, #293, #306, #316–#319, #321, #332). The window opened with twenty VTI crates and two VGI ones under [patch.crates-io], waiting on the persona/facet/* tasks and a vetting feature; it closed on 2026-09-10 (#291, vta-sdk 0.35, patch block deleted and deny.toml’s allow-git with it, “so reintroducing one is a visible decision”). Then 0.36, 0.37 (generated vetting types), 0.38 with did-git-sign 0.4.10 (#321 — the last duplicate sdk and the second didwebvh-rs gone), 0.39, and 0.42.1. The constraint that outlived the merge: vta-sdk ≥ 0.35 refuses an unsigned trust-task reply by default, so any VTA this build talks to must be vta-service 0.25+.
  • Trust Tasks in the binding envelope, both directions (#327–#331). OPENVTC_CATCH_ALL_PATTERN listed task URIs only, so the binding envelope replies the VTC sent to capability writes were dropped between a sender and a handler that both existed (#328). Four sends had typed their DIDComm message with the task URI instead of the envelope, and two (members/self-remove, members/vmc) sent bare payloads that bypassed the VTC’s signing spine (#331). One trust_task_doc::build_value replaces three builders (#330); a census test fails on a forgotten routing prefix (#329).
  • TSP Rev 3 as a first-class citizen (#333 — the Eucalyptus-RC-0 commit). Rev 3 §7.2.2 gates application traffic on a recorded relationship: a peer the endpoint holds none with has its messages silently dropped. OpenVTC now forms the relationship (form_relationship_routed, idempotent) before any VTC Trust Task and after enable_tsp_trust_tasks on the VTA leg; recognizes inbound RelationshipControl; and persists relationships in a config-backed PersistentRelationshipStore mirrored into ProtectedConfig. A TSP Relationships pane and a health finding name the “no relationship → dropped” case that explains a join to a healthy VTC that gets no reply. The same commit takes the Rev 3 frontier — vta-sdk 0.42.1, affinidi-tdk 0.16, messaging-sdk 0.26.7, affinidi-tsp 0.2.1, trust-tasks 0.21.3, dtg-credentials 0.9.1 (Working Draft 02: the member VMC’s grant binding moves from sha256:<hex> digest to base58btc digestMultibase — a wire change the whole stack flips together; and vta-sdk 0.42 itself now depends on dtg-credentials, so the pin follows the sdk).

After RC-0 (2026-09-17/18, #334–#343)

Why a membership ended is now persisted — code, reason, authority, time from the deny verdict, the FORBIDDEN problem-report, the join-denial error, or the status poll — and shown as a Reason: row; vtc/members/removal-notice/0.1 is the first path that moves a membership to Removed (#334, closes #240). A community’s relationshipIdentifierDefault is read via vtc/community/profile/show/0.1 and seeds the relationship form (#335, closes #241; explained on the form, #338). An oversized join submit fails loudly instead of being silently dropped by a bridge size limit — the root cause behind PR #137, deferred since (#339). Joining a community that vets offers every way in at once — invitation, vetting, open request — each marked available or not and why, and j on an application takes up the join it was made for, so vetting reads as a way of joining rather than something to do before it (#342, #343). A first join from an account with no inbound arm now says the community could not be asked rather than falling through to an open request in silence. The Vetting page guides to creating a persona in-panel and brings a new one online at runtime (#337).

Still open at RC-0 (tasks/follow-ups.md): D4 (VP construction — the vetting spec supplies the requirement model it lacked); persona key rotation; consuming vtc-client for the join ceremony and deleting the hand-built join.rs/tsp.rs path (VTI #1399 fixed the blockers, waiting on a release); verifying VTC trust-task reply proofs (waiting on a vtc-service release past 0.11.58); the identity pane’s four deliberately-omitted persona/* verbs; the vetting spec’s V0 (5 Oct). Of these, reply-proof verification (#380–#392), the omitted persona/* verbs (#370) and V0 (0.4.0, 10-04) closed in the Eucalyptus window.

v0.3.0 — 2026-08-15 — the join ceremony’s asynchronous half; Cypress — 2026-08-17

The first tagged release since v0.2.0 (21 May) — 0.2.1 was written up but never tagged, so it ships here too. The theme: a community’s reply must reach the applicant whether or not it happened to be connected when the reply was sent, and a join left unresolved is reconciled by asking rather than waiting. Four fixes, all to the same class of bug (a join sits Pending while the community’s outbox reports Sent, and relaunching the app “fixes” it):

  • Connect the applicant persona before submitting (#217) — auto-admitted joins returned VMC + VEC in under a second while the persona’s socket came up ~29 s later, so both credentials were stored at the mediator and never pushed.
  • Collect stored mail on connect (#218) — a mediator live-streams only to a recipient connected at that instant, and redelivers a stored inbox only when a new socket displaces an old one; Messaging::pickup_stored now drains message-pickup 3.0 on every connect (ack after handoff, 200 per connect), and the messaging runtime starts before the State-A branch so a first-run join has a live socket.
  • Ask a community about a join it has not answered (#219) — join-requests/status/0.1 had only its receiving half implemented; a minute tick now polls each Pending join with an immediate poll at launch, per-record backoff 1 → 2 → 4 → 8 min capped at 15, ≤4 polls per tick; the community’s own requestId is adopted from refer/request_more verdicts (CommunityRecord::request_id_confirmed); the poll takes the transport the submit took.
  • 0.3.1 (08-16, untagged but at Cypress): a first-run join could never be answered (#221); openvtc health with live progress (#222); poll a join without knowing its request id (#226, with VTC #985); prefill the community from a pasted VIC and drop a phantom setup step (#227); an explicit paste row and 13 unreachable setup pages (~3,800 lines) removed (#228); say so when a mint comes back without TSP (#223); never drop an inbound message already acked (#224).

Cypress: VTI-Cypress-RC-0 (07-31, #203), VTI-Cypress-RC-1 (08-11, #210), Cypress (08-17, #229 — trust-tasks 0.9 / vta-sdk 0.25 / did-git-sign 0.4.5, workspace 0.3.1). Post-Cypress (#230–#237): VIC list and startup listeners moved off the event loop, State-A fixes (agent names, identity removal, settings before a community exists, #235), exhaustive action matches so loop divergence cannot recur (#236), agent-name verbs off the state-handler loop (#237, first “R14” perf batch), the TDK auth-refresh fix (#232). Caveat: the CHANGELOG does not cover #152–#203 (agent names, VGI, capabilities, delivery, TSP) — commit history is ground truth for those.

Agent names — 2026-07-22/23 (#163–#182)

OpenVTC is the display side of the ecosystem’s agent-names story (the TDK resolves, did-hosting-service serves /@name). openvtc_core::agent_name delegates to the TDK agent-names crate; a name is shown only if it forward-resolves and round-trips back to the labeled DID (spoof guard), cached in ProtectedConfig (24 h TTL, 5-min negative TTL) with an off-loop refresh sweep. Precedence on every DID surface: user alias → verified agent name → truncated DID (#165, #174–#182, #179). Input: the join VTC-DID entry and relationship requests accept example.com/@name (#166) — the DID is persisted, never the name. Personas’ names are managed from the VTA panel via six did-management/agent-name Trust Tasks with a confirm-before-remove (#167, #169). Three rules were added to the repo’s CLAUDE.md.

Capabilities, TSP, delivery layer, VGI — July 2026

  • Per-community Capabilities panel (#157/#158): governance/capability/list over DIDComm; enabled/available/delegated rows, manifest detail, enable/disable documents signed eddsa-jcs-2022 by the persona; wire code from the published trust-tasks-capability-client.
  • TSP as a live transport: the VTA panel stops misreporting a TSP VTA (#187); trust tasks over a TSP leg of the session with bounded discovery and DIDComm degrade (#196); the join ceremony over TSP when the community offers it (#201; openvtc_core::tsp); reopen TSP after bootstrap rather than falling back to REST (#207); record the submit transport and refuse a community that advertises none (#210); choose TSP only when our own mediator can carry it (#211); add_tsp_service: true on mint (#213).
  • Messaging rebuilt on the delivery layer (#191–#195): the DIDComm transport moved into openvtc-core, ListenerSpec replaces the framework type, production messaging runs on affinidi-messaging-delivery (one DidCommTransport per identity, Delivery::Guaranteed outbox, dispatch_inbound replaces the Router; affinidi-messaging-didcomm-service dropped), and a supervisor rebuilds a listener whose ATM died.
  • DCQL consent (#197): openvtc_core::presentation — held credentials → evaluate_query (DCQL) → DisclosureRequest → present (vp_token). Re-scoped the old D4 open item: the verified path is credential-exchange/query|present, not the submit-slot VP.
  • VGI / did-git-sign (#152–#160): did-git-sign verify-trust (sshsig parsing, DID-document key match, TRQP authorization against the VTC Trust Registry, fail-closed), PGP exemption keyring + composite GitHub Action, org-fallback grants + committed web-flow platform keyring — then the whole thing extracted to verifiable-git-infrastructure and consumed as a published crate (#159), with the Action pinned (#160).
  • Also: the spec/vtc Trust-Task registry authority followed (#171–#173, #190); untrusted strings cut on character boundaries, not byte offsets — a remote panic found by fuzzing (#202); completed task trackers archived (#162), cross-service networking discipline recorded (#161).

June 2026 — multi-community executed (T1–T9 complete) + VIC join flow — Banyan milestone

The entire multi-community plan landed in a single month (80 commits, PRs #69–#149; ~21,600 insertions). The window ends with a lightweight milestone tag, Banyan (2026-06-22, at PR #148) — note the shift from vX.Y.Z tags to codenames. At the time the workspace version was still 0.2.1 and the changelog hadn’t caught up (it did in v0.3.0); commit history, the DRAFT v5 spec, and the tag were ground truth.

T1–T9 all complete. Beyond the T1 foundation (config v2 + supervised multi-session manager, one recoverable DIDComm task per community session, 111), the slices landed in order: context_path hierarchy mirroring vti-common validation (T2, #112); State-A bootstrap split from the monolithic ~19-step wizard — account bootstrap mints no persona DID (T3, #113); communities overview with favorite toggle and a Ctrl+K community switcher (T4, #114); join flow with identity choice — reuse a persona or mint a fresh did:webvh — and live session registration without restart (T5, 116); pending-resolution lifecycle with the 7-day timeout → Expired (T6, #117); leave / archive / inactive-only delete with read-only styling (T7, #118); did-git-sign per-repo persona selection (T8, #119); and end-to-end integration tests against a real mediator and VTI’s MockVta (T9, #120–#122).

The join path got real — Verifiable Invitation Credentials + verdict-model admission. The spec’s “stub VP” placeholder (deferred decision D4) was replaced within the same window by a concrete credential mechanism:

  • Join over DIDComm: submit_join to the VTC (#69), submit-receipt reconciliation (#71), credential delivery flipping Pending → Active (#72), minted-persona rollback on failed join (#74, #123), one DIDComm listener per persona (#79) — the messaging backbone of multi-community.
  • VIC presentation at join (#127), VIC storage in the VTA credential vault (#130), subject-linkage proof to join under a fresh DID (#131), full VIC lifecycle management from the VTA panel (import/archive/soft-delete/restore/purge, #136).
  • Verdict-model join (#136): a VerdictResponse drives admission — allow → Active, deny → Rejected, refer/request_more → Pending — plus DIDComm problem-report handling, fixing joins that previously sat stuck Pending for 7 days on rejection.
  • Join UX/robustness burst (#137–#145): community-matched VIC selection, Trust Task document framing, W3C-compliant join VP, identity-first join with per-persona VIC badges.
  • Multi-membership + reciprocity: multiple memberships per community under distinct personas (#146); issuing a reciprocal member VMC back to the community (#147) and auto-answering a VTC members/request-vmc (#148 — the Banyan commit). Membership is now bidirectional in practice, matching the spec’s bi-directional edge model.

Supporting streams in the same window:

  • R-series architectural hardening (R17–R27, #89–#109): protocol state machine hoisted into openvtc-core (new 1,800-line messaging.rs), Arc<Mutex> flattened out of the domain model, typed credential registry replacing string-matched VC kinds, typed VTA/Auth errors, a TUI architecture design doc.
  • TUI responsiveness (#92–#97): background-dispatch pattern for network actions, coalesced/offloaded Config::save, Argon2 KDF off the async runtime, cancelable mid-flight join/setup.
  • Security & privacy fixes (#82–#88): VRCIssued now gated on relationship, issuer binding, and proof verification; private-config contents no longer logged; DIDs truncated in handler logs; private key redacted from did-git-sign debug output; fuzzing groundwork (#125/#126) with feature-gated Arbitrary derives on parse-surface types.
  • Final fix of the window (#149, 06-27): repair R-DID key_info ids that caused a mediator auth loop.
  • Dependency escalation tracking VTI’s cadence: vta-sdk 0.10 → 0.17 → 0.18.1 plus affinidi-tdk 0.8 in ten days — OpenVTC is functioning as the reference client for the VTI stack.

Still open at the time: VP requirement discovery (D4 — re-scoped in July by #197 to the credential-exchange/query|present path), persona key rotation (still open), per-community capabilities (shipped July, #157).

Post-v0.2.1 — multi-community design + T1 implementation

Design and implementation of OpenVTC’s multi-community pivot are landing in slices on top of v0.2.1.

  • Multi-community design spec (PR #65, 2026-06-03) — docs/design/multi-community-support.md DRAFT v4 + presentation deck. Decisions D1–D17 settle persona-per-community choice (user picks per join, lazy persona creation), VTA-as-system-of-record (the local config holds only references and UX prefs; the VTA stores personas/keys/credentials), breaking-reset migration (v1 configs are not migrated; the CLI detects, informs, deletes, and runs setup from scratch), per-community read-only / archive / delete lifecycle, supervised concurrent sessions (one supervised task per community session, failure-isolated), 7-day client-side pending-join timeout, did-git-sign per-repo persona selection. tasks/plan.md + tasks/todo.md give the dependency-ordered T1–T9 breakdown across four phases.
  • T1 active-identity API sketch (PR #66, 2026-06-04) — pins IdentityContext / IdentityRegistry (in openvtc-core) and the persona-keyed multi-session manager built as a thin layer over affinidi-messaging-didcomm-service 0.3.3 (whose listeners are already independent, auto-restarting tasks with dynamic add/remove). N=1 single-community on the new architecture is T1’s exit criterion.
  • VTA hierarchical contexts + MockVta folded in (PR #68, 2026-06-04) — VTI shipped server-enforced hierarchical context paths and a MockVta test harness; the multi-community spec dropped its “convention now, migrate later” sub-context fallback in favor of the canonical server-enforced model (D2/D9 updated). Spec → DRAFT v5.
  • T1 implementation slice (PR #67, 2026-06-07) — additive config v2 in openvtc-core: Account { vta_did, vta_url, top_context_id, personas, communities }, PersonaRecord (self-contained did:webvh, VTA-managed KeyRefs), CommunityRecord (persona_ref, status, favourite, archived, member_since, per-community relationships and VRCs), CommunityStatus { Pending, Active, Left, Rejected, Removed, Expired } with is_active/is_read_only/is_inactive/needs_attention, stable-UUID PersonaId (rotation-safe). Persona-keyed IdentityRegistry resolves communities → personas; a reused persona yields one shared DIDComm session serving multiple communities (matches the DIDComm DuplicateDid constraint and the chosen isolation model). Still additive — not yet wired into the live Config / load / save path; the consumer refactor, IdentityContext, and breaking-reset land in subsequent slices.

v0.2.1 — 2026-06-03 — nine CLI security/hardening fixes + crate retirement

  • Nine CLI security and hardening fixes (PR #61)
  • Minor dependency updates (PR #60); arboard::Clipboard import fix; vta-sdk bump to 0.7 (PR #62)
  • openvtc-service and robotic-maintainers crates removed (PR #63). The DIDComm daemon role is covered by the TUI’s own DIDComm session integration; the auto-VRC-issuance fixture is no longer needed alongside the in-tree mediator harness. Workspace members, README, CONTRIBUTING, and SECURITY updated. Historical CHANGELOG entries preserved. Closes #21 (the secure-storage-backend work for openvtc-service is obsolete).
  • Workspace bumps: vta-sdk 0.7 → 0.9; criterion 0.7 → 0.8 (PR #64).

v0.2.0 — 2026-05-05 — major release (workspace consolidation + full TUI)

Headline functional changes:

  • Workspace consolidation: the active CLI binary openvtc-cli2 renamed to openvtc (matching the uv / ruff / deno / cargo convention); the supporting library openvtc-lib renamed to openvtc-core; the legacy prompt-driven openvtc-cli deleted
  • Full TUI main menu — eight panels: Inbox, Relationships, Credentials, Settings, VTA Service, Logs, Help/Status, Quit. Real-time inbox task processing, inline alias editing, R-DID privacy toggle, trust-ping with RTT latency, raw VRC JSON in detail view, scrollable activity log
  • DIDComm service integration replacing ~370 lines of manual messaging with affinidi-messaging-didcomm-service 0.2 (Router-based dispatch, automatic reconnection, message pickup, multi-DID listener support, periodic 60s keepalive)
  • R-DID generation for both BIP32 and VTA backends, with dynamic R-DID listeners auto-added so message delivery works to relationship-specific DIDs
  • VRC issuance from inbox with DataIntegrityProof signing; VRC rejection with message back to requester; friendly name in relationship requests, auto-set as contact alias on accept
  • ratatui and dialoguer dependencies removed from openvtc-core so daemon and automation crates no longer pull in TUI deps transitively

Security pass folded into the same release branch:

  • Per-entry random Argon2 salt with transparent v1→v2 migration for derive_passphrase_key. The previous deterministic salt = SHA-256(info) meant two operators with the same passphrase produced byte-identical KEKs; the new passphrase_encrypt_v2 writes a magic-prefixed [OPV2 | salt(16) | nonce(12) | ct+tag] blob with a fresh random salt; decrypt auto-detects v1/v2. Argon2id parameters bumped to the OWASP “high-value KEK” floor (m=128 MiB, t=4, p=1)
  • did-git-sign signing-policy hardening: the proxy refuses to sign unless the parent process name starts with git or ssh-keygen, and writes every signing attempt (accepted or denied) to ~/.config/did-git-sign/audit.log (mode 0600)
  • DIDComm replay window + 1024-entry seen-message LRU in process_inbound_message: drop messages outside ±48h / +5m skew, drop expired, dedupe by ID
  • Tagged-variant downgrade defense on SecuredConfigFormat: switched the on-disk variant tag from #[serde(untagged)] to #[serde(tag = "format")] so every blob carries an explicit discriminator
  • W3C DID Core 1.0 syntax parser replacing the previous did: prefix check; rejects bidi-override / zero-width chars in DID fields
  • Inbox display-name sanitization strips bidi/control/ANSI; bounded DIDComm event channel (256-entry capacity); did.jsonl write path is now the resolved profile dir
  • Three community PRs folded in: profile-name validation hardening (#57); cross-platform config paths via dirs::config_dir() on Windows (#51, closes #47); SecuredConfig serde-format hardening (#34)

Test & CI changes:

  • New in-process mediator harness wrapping the upstream affinidi-messaging-test-mediator 0.2 fixture; drops ~400 lines of in-tree fixture code and four dev-deps
  • End-to-end integration tests drive a real Alice → Mediator → Bob DIDComm round-trip, a RelationshipRequestBody round-trip, and a two-leg VRC request/reject — all in ~350 ms once the mediator is up
  • 38 new unit tests: setup-flow navigation (25 table-driven), BIP32 derivation (7 known-answer vectors), AES-GCM tampering (6)
  • CI adds cargo-deny (advisories + licenses + bans + sources) and cargo-llvm-cov coverage; MSRV bumped 1.91 → 1.94

v0.1.6 (did-git-sign integration) — 2026-04-30

  • Auto-configure did-git-sign as part of setup wizard
  • Uninstall (lib + CLI subcommand)
  • Show did-git-sign principal + SSH key on help screen
  • SSH-aware clipboard with OSC 52 + arboard fallback
  • --credential paste replaced with online VTA provisioning
  • AdminRotated provisioning intent

v0.1.5 — 2026-04-15

  • BIP32 seed and imported key material moved from plain String to SecretString
  • Windows PowerShell examples and Windows secure-storage documentation

v0.1.4 — earlier April 2026

  • VRC valid-until prompt handling fix

v0.1.3 — earlier April 2026

  • Fixed a deterministic encryption vulnerability in unlock-code encryption (replaced seeded PRNG with HKDF-SHA256 + random nonce, with transparent legacy migration)

See also: verifiable-trust-agent, first-person-network, decentralized-trust-graph