pub struct QuestionDialer { /* private fields */ }Expand description
Reusable handle for the control plane’s QuestionService (#1660).
The THIN ask_question pause/resume path — the question-pause SIBLING of
ApprovalDialer, not a reuse of it: its own event kinds, its own
signed-answer canonical (polyc_crypto::question), so an approval
signature can never verify as a question answer and vice versa. Shares
the AgentService endpoint — all these services are served on one
Connect port — so it is built from the same address.
Implementations§
Source§impl QuestionDialer
impl QuestionDialer
Sourcepub fn new(addr: &str) -> Result<Self, DialError>
pub fn new(addr: &str) -> Result<Self, DialError>
Build a dialer pointed at addr (expects http://host:port),
carrying NO Authorization header.
#1660 incident (2026-07-28): this used to be the only constructor,
on the mistaken assumption that a Respond call’s per-question
answer_token was sufficient auth on its own. It authorizes THAT
answer, but it is not what the control plane’s require_edge_bearer
layer checks — that layer guards every RPC on this listener
regardless of the call’s own payload, QuestionService included. An
edge with real edge credentials MUST use Self::with_bearer
instead, or every answer submission fails closed with 401. This
constructor now exists only for the genuinely uncredentialed case
(mirroring ApprovalDialer::new).
§Errors
Returns DialError::InvalidAddress if addr isn’t a valid URI.
Sourcepub fn with_bearer(addr: &str, bearer: &str) -> Result<Self, DialError>
pub fn with_bearer(addr: &str, bearer: &str) -> Result<Self, DialError>
Build a dialer pointed at addr, authenticated with bearer.
Every call this dialer makes carries an Authorization: Bearer <bearer> header — required by the control plane’s
require_edge_bearer layer, which guards QuestionService like
every other RPC on this listener (see Self::new’s doc for why
this constructor exists). respond’s per-question answer_token is
a SEPARATE, per-call authorization on top of this — bearer proves
“this is a registered edge”, answer_token proves “this is a valid
answer to this specific question”.
§Errors
Returns DialError::InvalidAddress if addr isn’t a valid URI, or
DialError::InvalidBearer if bearer can’t be encoded as an HTTP
header value.
Sourcepub async fn respond(
&self,
turn_id: &str,
call_id: &str,
index: u32,
choice: QuestionChoice,
conversation_id: &str,
answer_token: &str,
responder: Option<ExternalIdentity>,
) -> Result<QuestionOutcome, DialError>
pub async fn respond( &self, turn_id: &str, call_id: &str, index: u32, choice: QuestionChoice, conversation_id: &str, answer_token: &str, responder: Option<ExternalIdentity>, ) -> Result<QuestionOutcome, DialError>
Submit a human’s answer for a pending question. The answer is
UNSIGNED — the control plane resolves and signs it server-side (THIN
path, mirrors ApprovalDialer::respond) and returns the signature.
Idempotent: answering an already-decided or unknown occurrence returns
persisted: false.
turn_id is the occurrence this answer names, carried unmodified off
the same card the answer_token came from (#2523). The control plane
refuses a Respond that names no occurrence: a provider re-mints a
tool-call id across turns, so (call_id, index) alone would let one
person’s decision land on a different question.
answer_token is the short-lived signed capability carried unmodified
off the TurnEvent::QuestionPending event or PendingQuestionPrompt
entry this decision answers — required: the control plane rejects a
Respond whose token is missing, expired, or bound to a different
question or conversation.
responder is the identity of the human answering — the durable
answered_by fact. An edge that cannot supply caller identity passes
None. Unsigned, unlike ApprovalDialer::respond’s: answering a
question authorizes nothing, so no gate reads this.
§Errors
Returns DialError::Connect for any transport/encoding error
(including the control plane’s rejection of an invalid
answer_token).
Sourcepub async fn list_pending(
&self,
conversation_id: &str,
) -> Result<Vec<PendingQuestionPrompt>, DialError>
pub async fn list_pending( &self, conversation_id: &str, ) -> Result<Vec<PendingQuestionPrompt>, DialError>
List the conversation’s outstanding questions (a question_request
with no later question_response). An edge calls this to recover
occurrences it must prompt on after losing the streamed
TurnEvent::QuestionPending event — mirrors
ApprovalDialer::list_pending. Read-only and idempotent.
§Errors
Returns DialError::Connect for any transport/encoding error.
Trait Implementations§
Source§impl Clone for QuestionDialer
impl Clone for QuestionDialer
Source§fn clone(&self) -> QuestionDialer
fn clone(&self) -> QuestionDialer
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for QuestionDialer
impl !UnwindSafe for QuestionDialer
impl Freeze for QuestionDialer
impl Send for QuestionDialer
impl Sync for QuestionDialer
impl Unpin for QuestionDialer
impl UnsafeUnpin for QuestionDialer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request