From 5bca5e5da0515ef51decf78132d42c49e3f07268 Mon Sep 17 00:00:00 2001 From: Glenn Gore Date: Fri, 18 Sep 2026 08:52:49 +0200 Subject: [PATCH] docs(tasks): scope the vtc-client / messaging-layer migration (blocked, not a drop-in) The backlog framed "consume vtc-client and delete the hand-built join path" as an unblocked consumer change once VTI #1399 shipped. Verifying against vtc-client 0.6.7 / vta-sdk 0.42.1 source shows it is not a drop-in: vtc-client's session verbs open their own mediator socket per DID (displacing OpenVTC's per-identity listener and breaking inbound), and the REST verbs need a VTC REST base URL the join flow never discovers (regressing DIDComm/TSP-only VTC support). The real deliverable is migrating OpenVTC's whole messaging layer onto vta-sdk sessions. Add `tasks/vtc-client-migration-scoping.md` (mirrors `d4-scoping.md`): the two walls with source evidence, the coupled surface, the options (recommend keeping the hand-built path; do reply-proof verification as a separate conflict-free piece), a work breakdown if the full migration is scheduled, and the stale-REST comment correction to carry. Update `follow-ups.md` to point both affected items at the doc and record the blocker. Signed-off-by: Glenn Gore --- tasks/follow-ups.md | 28 ++++-- tasks/vtc-client-migration-scoping.md | 131 ++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 tasks/vtc-client-migration-scoping.md diff --git a/tasks/follow-ups.md b/tasks/follow-ups.md index 368bf23..fc805d5 100644 --- a/tasks/follow-ups.md +++ b/tasks/follow-ups.md @@ -5,8 +5,10 @@ R0–R27) when those were archived on 2026-07-20. Everything numbered in those plans shipped; what remains are small, unscheduled items that lived in the prose notes rather than as checkboxes. -Companion: [`d4-scoping.md`](./d4-scoping.md) — the one item large enough to -have its own scoping doc. +Companions — the items large enough to have their own scoping docs: +[`d4-scoping.md`](./d4-scoping.md) (VP construction) and +[`vtc-client-migration-scoping.md`](./vtc-client-migration-scoping.md) +(the messaging-layer migration behind "consume vtc-client"). Status legend: `[ ]` open · `[~]` in progress · `[x]` done @@ -80,13 +82,23 @@ beside the `vta-sdk` line in the root manifest; has to be reversed. ### [ ] Consume `vtc-client` for VTC interactions and delete the hand-built path +**Blocked on an architectural mismatch, not an upstream release — see +[`vtc-client-migration-scoping.md`](./vtc-client-migration-scoping.md).** VTI #1399 +is in (`vtc-client 0.6.7` has `submit_join_as` + `connect_didcomm`/`connect_tsp`), +but verifying against the source showed the drop-in cannot work: vtc-client's +session verbs open their **own** mediator socket per DID ("one socket per DID"), +which would displace OpenVTC's per-identity listener and break inbound; and the +REST verbs need a VTC REST base URL the join flow never discovers (VTCs are +addressed by DID over DIDComm/TSP), so REST-only would regress transport support. +Consuming vtc-client therefore means migrating OpenVTC's whole messaging layer +onto `vta-sdk` sessions — its own initiative, scoped in that doc. + +Original framing (kept for context — its premise does not hold): `openvtc-core/src/join.rs` builds the join-ceremony Trust Task documents itself and sends them over ATM or `tsp::send_trust_task`, because the library offered a transport we could not use and a signature we could not satisfy. **VTI #1399 fixed both** (`vtc-client` gained `submit_join_as` for any DID method, and -`connect_didcomm` / `connect_tsp` behind off-by-default features), so this is now -a consumer change waiting on a `vtc-client` release carrying it — and on the -0.35 line above, since `vtc-client` on `main` declares `vta-sdk` 0.35. +`connect_didcomm` / `connect_tsp` behind off-by-default features). What goes when it lands: `submit_join_request`, the status poll and self-remove in `join.rs`, our half of `tsp.rs`, and the DIDComm-vs-TSP thread-id @@ -113,7 +125,11 @@ Gated on a `vtc-service` release carrying #1334: `main` is still at **0.11.58**, the same version published 2026-08-11, a month before the fix. Built today it would have to ship default-off, which is a switch with nothing to switch on. Falls out for free if the item above lands first, since `vtc-client`'s session -transports verify reply proofs through the SDK. +transports verify reply proofs through the SDK — but the item above is now a +whole messaging migration (see the scoping doc), so this is better done on its +own: verify the inbound reply's DI proof on OpenVTC's existing inbound path with +the `affinidi-data-integrity` verifier the VRC path already uses, no transport +change. That is conflict-free and does not wait on the migration. --- diff --git a/tasks/vtc-client-migration-scoping.md b/tasks/vtc-client-migration-scoping.md new file mode 100644 index 0000000..b6b257a --- /dev/null +++ b/tasks/vtc-client-migration-scoping.md @@ -0,0 +1,131 @@ +# Consuming vtc-client for VTC interactions (scoping) + +Status: **scoping** (not scheduled). Covers the two intertwined backlog items — +"Consume `vtc-client` for VTC interactions and delete the hand-built path" and +"Verify VTC trust-task reply proofs" (see [`follow-ups.md`](./follow-ups.md)). + +## 1. What this is, and why it is NOT a drop-in + +The backlog framed this as an unblocked consumer change: VTI #1399 shipped +`vtc-client`'s `submit_join_as` (any DID method) and `connect_didcomm` / +`connect_tsp` behind off-by-default features, so the hope was to delete the +hand-built join transport in `openvtc-core/src/join.rs` (submit / status-poll / +self-remove), our half of `tsp.rs`, and the DIDComm-vs-TSP thread-id +reconciliation, keeping `message_dispatch.rs` for inbound. + +**It is not a drop-in.** Verified against `vta-sdk 0.42.1` /`vtc-client 0.6.7` +source, two independent walls: + +1. **vtc-client's session verbs open their own per-DID socket.** + `VtcClient::connect_didcomm`/`connect_tsp` → `vta_sdk::client::VtaClient:: + connect_didcomm` → `DIDCommSession::connect(...)`, a fresh mediated session. + The SDK says it plainly: "each client gets its own profile and its own + mediator websocket (the mediator's ceiling is one socket per DID)". OpenVTC + already holds exactly one listener per persona (`didcomm::build_listener_configs`, + one per identity; and #337 now installs a persona's listener at runtime). A + second socket for the same persona DID **displaces** OpenVTC's listener, so + the join reply and all later inbound land on vtc-client's ephemeral session, + invisible to `message_dispatch`. This is the `stored-mail-never-collected` / + `listener-socket-leak` failure class. +2. **The REST verbs avoid the socket but regress transport support.** + `submit_join_as` over HTTPS opens no socket, but the join flow addresses a VTC + *by DID* over DIDComm/TSP and never discovers a REST base URL + (`join_flow.rs` resolves the mediator / `peer_tsp_mediator`, and warns when a + community "advertises no messaging transport"). Many VTCs are DIDComm/TSP-only. + REST-only submit drops them; REST *alongside* the hand-built path is more code, + not less. + +**Root cause:** an architectural mismatch, not an upstream gap. `vtc-client` is +built for a caller that owns its VTC sessions through `vta-sdk`; OpenVTC owns its +transports through `affinidi-messaging-delivery` (one per identity + durable +outbox, #194). The two are different messaging stacks, and running both for the +same DIDs is the conflict above. + +## 2. Consequence: the real deliverable is a messaging-layer migration + +To consume vtc-client's session verbs without breaking inbound, OpenVTC's +**entire** persona messaging layer would move onto `vta-sdk` sessions — one +`SessionHub` for the account, one `VtaClient` per persona on it +(`connect_didcomm_on`), so a persona has exactly one socket that BOTH the VTC +verbs and inbound dispatch share. That is a large initiative, not a backlog +cleanup, and it subsumes the "verify reply proofs" item for free (an SDK session +verifies reply proofs — `require_signed_replies: true` by default). + +## 3. What is coupled (the migration surface) + +- **Outbound VTC verbs** (`openvtc-core/src/join.rs`): `submit_join_request` + (+ the #339 oversize guard), `poll_join_status`, `send_community_profile_show` + (#335), and `MEMBER_SELF_REMOVE` — each currently `build_trust_task_document` + + `pack_and_send` / `tsp::send_trust_task`. +- **The transport** (`openvtc-core/src/didcomm.rs`, `tsp.rs`): `build_listener_configs` + (one listener per identity), `pack_and_send`, `peer_tsp_mediator`, the + per-persona/-relationship listeners on `affinidi-messaging-delivery`. +- **Session tracking** (`state_handler/session_manager.rs`, `mod.rs`): the + session manager, `register_joined_session`, `install_persona_listener` (#337), + the reconcile tick, the connection indicator. +- **Inbound dispatch** (`state_handler/message_dispatch.rs`): reads OpenVTC's ATM + listeners today; would read the shared SDK session instead. This is where the + reply-proof verification lands. +- **The durable outbox** — `affinidi-messaging-delivery`'s guarantee (D1). Any + replacement must not lose the "sent-but-unacked survives a restart" property. + +## 4. Options + +- **(A) Do nothing — keep the hand-built path.** Lowest risk. The hand-built + transport works and is guarded (#339). Cost: the stale-REST comment in + `join.rs` stays wrong (see §6), and the thread-id reconciliation stays a + consumer concern. +- **(B) REST for VTCs that advertise it, hand-built otherwise.** Adds a REST + discovery + `submit_join_as` path *alongside* the existing one. More code, + narrow benefit (skip a mediator hop for REST-capable VTCs); does not delete + anything. Not recommended. +- **(C) Full migration to `vta-sdk` sessions (`SessionHub`).** The only path that + actually deletes the hand-built transport and unlocks library-verified replies + — but it replaces OpenVTC's messaging foundation. Its own initiative. + +**Recommendation:** **(A) for now**, and if the reply-proof integrity of inbound +VTC replies is wanted sooner, do that as a *separate, conflict-free* piece (verify +the inbound reply's DI proof on OpenVTC's own inbound path with the +`affinidi-data-integrity` verifier the VRC path already uses — no transport +change). Pursue (C) only as a scheduled initiative. + +## 5. If (C) is scheduled — work breakdown + +- **C.0 — decide durable-outbox story.** Does the `SessionHub` model preserve + `affinidi-messaging-delivery`'s durable outbox (D1), or is a replacement needed? + Blocking; everything else assumes an answer. +- **C.1 — stand up a `SessionHub` per account**, one `VtaClient` per persona via + `connect_didcomm_on` / `connect_tsp` (mirror `build_listener_configs`' per-identity + set, plus per-relationship R-DIDs). +- **C.2 — route inbound off the shared session** into the existing + `process_inbound_message`, with reply-proof verification on (closes the + "verify reply proofs" item). +- **C.3 — move the outbound verbs** (join submit/status/self-remove, profile-show, + relationship/VRC sends) onto the `VtaClient`; delete `join.rs`'s hand-built + senders, our half of `tsp.rs`, and the thread-id reconciliation. Re-home the + #339 oversize guard (the SDK submit path needs its own guard, or keep a + pre-flight check). +- **C.4 — session lifecycle**: reconcile `session_manager`, `register_joined_session`, + runtime persona install (#337), token-refresh reconnects (see + `listener-flapping` history) against the hub's lifecycle + `shutdown` contract. +- **C.5 — tests + live validation**: the MockVta transport harness is WIP/blocked + (`tasks/` + memory), so this needs it unblocked, or a live VTA. Every ignored + join/relationship/mediator e2e becomes runnable. + +## 6. Corrections to carry (true regardless of option) + +- `openvtc-core/src/join.rs`'s header says REST is unusable for a `did:webvh` + persona because "the VTC's REST holder-binding verification accepts `did:key` + applicants only." That describes the retired per-verb `holder_signature.rs` + binding; the current document endpoint resolves the proof's `verificationMethod` + through a DID resolver and has accepted `did:webvh` since the vm-resolver work. + Fix that comment whenever this area is next touched. + +## 7. Dependencies / sequencing + +- No new dep: `vtc-client 0.6.7` (features `didcomm`/`tsp`) and `vta-sdk 0.42.1` + (`SessionHub`, `connect_didcomm_on`) are already resolvable in the tree. +- (C) is gated on C.0 (durable outbox) and on a working transport test harness + (MockVta, currently blocked) or a live VTA. +- (C) touches the config-adjacent messaging foundation but not the config model, + bootstrap, or lifecycle reducers already shipped.