Surfaced by an external RAHP/DRARM assessment of the Cypress release
(sankarshanmukhopadhyay/rahp-toolkit#18, finding OVTC-RAHP-03). This issue
covers the client half only; the governance half (appeal, review,
restoration) belongs in VTC/VTI and is tracked separately.
Problem
When a VTC rejects a join request or removes an active member, the decision
evidence reaches us on the wire and we throw it away.
VTC returns a typed reject code plus a reason
(vtc-service/src/messaging.rs:974 in verifiable-trust-infrastructure). All
four inbound paths in openvtc-core/src/messaging.rs have that code and reason
in hand:
:308 — status: "rejected" on a join-status reply
:392 — VerdictEffect::Deny, which even logs code and reason
:473 — FORBIDDEN problem-report (invitation not accepted)
:564 — join-denial trust-task-error, which logs code and detail
Each one then calls record.reject(), and
CommunityRecord::reject() / remove() take no arguments
(openvtc-core/src/config/account.rs:441-454). They set a status and clear
acknowledged. Nothing else is persisted.
The net effect for the operator: a community flips to Rejected or Removed,
the actions-required badge lights up, they press a key to acknowledge it — and
the why exists only in a tracing line they will never see. There is no
record of who decided, on what basis, or when. Acknowledging an outcome you
cannot inspect is not meaningful acknowledgement.
Expired has the same shape but is client-generated, so it is less severe —
still worth carrying the timeout basis for consistency.
Proposed change
- Give
reject() and remove() a decision-evidence argument and persist it on
CommunityRecord:
- deciding authority (the VTC DID that sent the decision)
- decision code (typed) and human-readable reason
- decision timestamp (theirs if present, ours as received otherwise)
- the inbound path it arrived on, so the evidence is reconstructable
- Surface it wherever the terminal state is shown — the communities list detail
view and the actions-required notice — not only in the acknowledge prompt.
- Keep it stable across the config round-trip (serde) so history survives a
restart; inactive records are already retained for re-join
(docs/design/multi-community-support.md R-S-1), and the evidence should be
retained with them.
- Where no reason is supplied by the VTC, say so explicitly ("no reason given")
rather than rendering an empty field — absence of a reason is itself
information the operator should see.
Out of scope
Appeal/review endpoints, policy-version context, and correction/restoration
semantics after an overturned decision. Those need the governing layer to define
them first; this issue makes OpenVTC ready to surface them.
Acceptance
- A rejected join and a removal each persist and display authority, code, reason
and timestamp.
- A decision arriving with no reason renders explicitly as such.
- Round-trip test: evidence survives save/load.
- No decision evidence is lost between the four inbound paths — a test per path.
Surfaced by an external RAHP/DRARM assessment of the Cypress release
(sankarshanmukhopadhyay/rahp-toolkit#18, finding
OVTC-RAHP-03). This issuecovers the client half only; the governance half (appeal, review,
restoration) belongs in VTC/VTI and is tracked separately.
Problem
When a VTC rejects a join request or removes an active member, the decision
evidence reaches us on the wire and we throw it away.
VTC returns a typed reject code plus a reason
(
vtc-service/src/messaging.rs:974inverifiable-trust-infrastructure). Allfour inbound paths in
openvtc-core/src/messaging.rshave that code and reasonin hand:
:308—status: "rejected"on a join-status reply:392—VerdictEffect::Deny, which even logscodeandreason:473—FORBIDDENproblem-report (invitation not accepted):564— join-denialtrust-task-error, which logscodeanddetailEach one then calls
record.reject(), andCommunityRecord::reject()/remove()take no arguments(
openvtc-core/src/config/account.rs:441-454). They set a status and clearacknowledged. Nothing else is persisted.The net effect for the operator: a community flips to
RejectedorRemoved,the actions-required badge lights up, they press a key to acknowledge it — and
the why exists only in a
tracingline they will never see. There is norecord of who decided, on what basis, or when. Acknowledging an outcome you
cannot inspect is not meaningful acknowledgement.
Expiredhas the same shape but is client-generated, so it is less severe —still worth carrying the timeout basis for consistency.
Proposed change
reject()andremove()a decision-evidence argument and persist it onCommunityRecord:view and the actions-required notice — not only in the acknowledge prompt.
restart; inactive records are already retained for re-join
(
docs/design/multi-community-support.mdR-S-1), and the evidence should beretained with them.
rather than rendering an empty field — absence of a reason is itself
information the operator should see.
Out of scope
Appeal/review endpoints, policy-version context, and correction/restoration
semantics after an overturned decision. Those need the governing layer to define
them first; this issue makes OpenVTC ready to surface them.
Acceptance
and timestamp.