Both halves of a storefront chat around domain-conversation-engine. Inbound: windows a thread to the turn being answered, resolves history down to referents so historical utterances never reach intent selection, projects catalogue values to typed tokens before generic DLP masking, and admits only registry-valid classifier output as candidates. Outbound: stores a turn and the buttons it offered as one transaction, and redeems a tapped button exactly once. It detects nothing and masks nothing; it owns one table, conversation_offers, because a capability's writer and its redeemer are both here.
| Tier | application |
| Role | pipeline |
| Path | crates/application/conversation |
| Edition | 2021 |
| Targets | application_conversation, pg_end_to_end, pg_offer_claim, pg_thread_store |
| Public items | 115 across 11 modules |
| Tests | 149 |
What it is for
Sprint 3.114: the two halves of a storefront chat that surround the decision engine — everything before a model sees a message, and everything after the engine has spoken.
Inbound (Story 2). A buyer types a question. Before any model sees it, this crate narrows the thread to the turn being answered, consumes the history down to referents, projects catalogue values to typed tokens, masks what is left, and checks whatever the classifier returns against the registry. Only then does domain_conversation_engine::respond decide what to say.
window -> resolve -> reduce -> guard -> classify -> admit -> respond
Outbound (Story 3). What the engine concluded becomes rows in the thread: the buyer's turn, the reply, and — when the bot declines — one fixed phrase for the buyer and a full account for the operator, on a conversation that is assigned rather than merely annotated. Buttons are stored as capabilities the server issued, and a tap redeems one by id.
plan_writes -> store -> deliver tap -> claim -> guard -> render
The two halves live together because the offer's writer and its reader are the same crate: a button is made on the way out and redeemed on the way in, and splitting them would put the capability's two ends in different places.
What this crate does not do. It detects nothing and masks nothing: screening is infrastructure-dlp-detect, the decision is domain-conversation-engine, the thread's tables belong to infrastructure-communication, and the model is whichever infrastructure-ai provider the host supplies. What it owns is the order those run in, and the two boundaries that make the order matter — reduce::resolve, where history stops being text, and admit::admit, where a model's answer stops being a claim.
It owns one table. migrations/0001_conversation_offers.sql creates conversation_offers, the capabilities a reply issued, because the button's writer and its redeemer are this crate and nothing else reads them. That is the whole of the schema it owns, and it stopped being schema-free in sprint 3.117 unit 3.
The effectful half (sprint 3.117). store holds the ports — a store::ThreadStore that puts one turn in the thread atomically and a store::Delivery that can only be handed a row already stored — plus store::store_then_deliver, which is the ordering rule as a function rather than as a sentence. postgres, behind the feature of that name, is the adapter over the schema owner's own functions and the offer claim. completion, behind the feature of that name, is classify: one model request per message, with the deadline and the byte ceiling that make a transport failure a typed error rather than a silent abstention. Everything above this paragraph compiles and is tested with neither.
On `domain-transcript-shard`. The inherited plan named it as this path's decomposition step. It is not used here, and the sprint doc records why: its Shard carries opaque speaker IDs and a court-transcript statement taxonomy and no words at all, so a classifier handed one cannot separate "is this solves a problem a chat does not have — a chat turn is labelled by role, so prose, and the prose is masked. Reusing it here would have been a dependency that satisfied a table rather than a need.
Capabilities
admit (other)
The enforcement boundary between a model and the engine.
| Item |
|---|
fn admit(raw : & RawCandidate, registry : & IntentRegistry,) -> Result <Vec <Candidate>, AdmitError> |
AdmitError
The enforcement boundary between a model and the engine.
| Item |
|---|
pub enum AdmitError |
RawCandidate
The enforcement boundary between a model and the engine.
| Item |
|---|
pub struct RawCandidate |
RawCandidate :: fn new(intent : impl Into <String>, score : f32) -> Self |
catalogue (other)
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
pub const DEFAULT_MAX_SLOT_LEN: usize |
BuyerAvailability
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
pub enum BuyerAvailability |
BuyerAvailability :: fn of(status : CatalogItemStatus) -> Option <Self> |
BuyerAvailability :: fn catalogue_key(self) -> & 'static str |
BuyerItemView
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
pub struct BuyerItemView<'a> |
BuyerItemView<'_>
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
BuyerItemView<'_> :: fn slot(& self, name : & str) -> Option <String> |
BuyerItemView<'_> :: fn subjects_in(& self, text : & str) -> Vec <String> |
BuyerItemView<'a>
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
BuyerItemView<'a> :: fn of(item : & 'a CatalogItem, viewer : & Viewer) -> Option <Self> |
BuyerItemView<'a> :: fn with_max_slot_len(mut self, max : usize) -> Self |
BuyerItemView<'a> :: fn slot_names() -> & 'static & 'static str |
Viewer
Filling a stored phrase from a catalogue record, without the record's
| Item |
|---|
pub struct Viewer |
Viewer :: fn may_see(& self, tier : Visibility) -> bool |
classify (other)
The Classifier over a one-shot completion provider.
| Item |
|---|
pub const DEFAULT_DEADLINE: Duration |
pub const DEFAULT_MAX_RESPONSE_BYTES: usize |
CompletionClassifier
The Classifier over a one-shot completion provider.
| Item |
|---|
pub struct CompletionClassifier |
CompletionClassifier :: fn fmt(& self, f : & mut std::fmt::Formatter <'_>) -> std::fmt::Result |
CompletionClassifier :: fn new(provider : Arc <dyn AiProvider>, registry : & IntentRegistry) -> Self |
CompletionClassifier :: fn with_deadline(mut self, deadline : Duration) -> Self |
CompletionClassifier :: fn with_max_response_bytes(mut self, bytes : usize) -> Self |
CompletionClassifier :: fn with_model(mut self, model : impl Into <String>) -> Self |
CompletionClassifier :: fn system_prompt(& self) -> & str |
CompletionClassifier :: async fn classify(& self, input : & ClassifierInput,) -> Result <Vec <RawCandidate>, ClassifierError> |
guard (other)
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub const DEFAULT_MAX_SNAPSHOT_AGE: Duration |
ConversationGuard
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub struct ConversationGuard |
ConversationGuard :: fn new(snapshot : WindowSnapshot, policy : IntentPolicy, registered : & & str,) -> Result <Self, UnclassifiedIntents> |
ConversationGuard :: fn with_max_age(mut self, max_age : Duration) -> Self |
ConversationGuard :: fn is_stale(& self, now : Instant) -> bool |
ConversationGuard :: fn permits(& self, intent : & str) -> GuardVerdict |
HoldFact
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub struct HoldFact |
HoldFact :: fn consumes_capacity(& self, now : DateTime <Utc>) -> bool |
HoldFact :: fn held_by(& self, party : & str, now : DateTime <Utc>) -> bool |
IntentPolicy
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub struct IntentPolicy |
IntentPolicy :: fn new() -> Self |
IntentPolicy :: fn with(mut self, intent : impl Into <String>, requires : Requires) -> Self |
IntentPolicy :: fn requirement(& self, intent : & str) -> Option <Requires> |
Requires
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub enum Requires |
UnclassifiedIntents
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub struct UnclassifiedIntents |
WindowSnapshot
Whether the conversation is in a state where an intent may be acted on.
| Item |
|---|
pub struct WindowSnapshot |
WindowSnapshot :: fn consumed(& self) -> u32 |
WindowSnapshot :: fn permitted(& self) -> u32 |
WindowSnapshot :: fn has_room(& self) -> bool |
WindowSnapshot :: fn party_holds(& self) -> bool |
offer (other)
A button is a capability the server issued, and a tap redeems it.
| Item |
|---|
fn verdict(offer : Option <& Offer>, at : & Redemption) -> OfferVerdict |
fn buyer_handoff_text() -> & 'static str |
Offer
A button is a capability the server issued, and a tap redeems it.
| Item |
|---|
pub struct Offer |
OfferId
A button is a capability the server issued, and a tap redeems it.
| Item |
|---|
pub struct OfferId |
OfferId :: fn new(id : impl Into <String>) -> Self |
OfferId :: fn as_str(& self) -> & str |
OfferVerdict
A button is a capability the server issued, and a tap redeems it.
| Item |
|---|
pub enum OfferVerdict |
Redemption
A button is a capability the server issued, and a tap redeems it.
| Item |
|---|
pub struct Redemption |
pipeline (other)
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
async fn handle_message(turns : & Turn, cfg : & Inbound <'_>) -> Outcome |
async fn handle_tap(offer_id : & OfferId, at : & Redemption, offers : & dyn OfferStore, templates : & Template, subject : & dyn SlotSource, transition_guard : & dyn TransitionGuard,) -> Outcome |
ClaimOutcome
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub enum ClaimOutcome |
Classifier
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub trait Classifier |
ClassifierError
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub enum ClassifierError |
ClassifierError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result |
Handoff
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub enum Handoff |
Inbound
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub struct Inbound<'a> |
OfferStore
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub trait OfferStore |
Outcome
The order the steps run in, and what happens when one of them says no.
| Item |
|---|
pub enum Outcome |
postgres (other)
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
async fn store_turn_in(conn : & mut PgConnection, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties,) -> Result <StoredTurn, StoreError> |
async fn thread_rows(pool : & PgPool, conversation_id : Uuid,) -> Result <Vec <StoredMessage>, StoreError> |
async fn store_turn_and_offers(pool : & PgPool, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties, terms : & OfferTerms, computed_under : Option <u64>,) -> Result <TurnCommit, StoreError> |
async fn take_over(pool : & PgPool, conversation_id : Uuid, operator : Uuid,) -> Result <u64, StoreError> |
NewOffer
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
pub struct NewOffer |
async fn insert_offer(pool : & PgPool, offer : & NewOffer) -> Result <(), StoreError> |
OfferTerms
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
pub struct OfferTerms |
async fn insert_offers_in(conn : & mut PgConnection, conversation_id : Uuid, writes : & ThreadWrite, stored : & StoredTurn, terms : & OfferTerms,) -> Result <Vec <OfferId>, StoreError> |
PostgresOfferStore
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
pub struct PostgresOfferStore |
PostgresOfferStore :: fn new(pool : PgPool) -> Self |
PostgresOfferStore :: async fn claim(& self, id : & OfferId, at : & Redemption) -> ClaimOutcome |
PostgresThreadStore
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
pub struct PostgresThreadStore |
PostgresThreadStore :: fn new(pool : PgPool) -> Self |
PostgresThreadStore :: async fn store_turn(& self, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties,) -> Result <StoredTurn, StoreError> |
PostgresThreadStore :: async fn mark_delivered(& self, message_id : Uuid) -> Result <(), StoreError> |
PostgresThreadStore :: async fn mark_delivery_failed(& self, message_id : Uuid, note : & str) -> Result <(), StoreError> |
TurnCommit
The crate::store::ThreadStore over infrastructure-communication's tables.
| Item |
|---|
pub enum TurnCommit |
reduce (other)
History in, referents out — then mask what is left.
| Item |
|---|
pub const SUBJECT_TOKEN: & str |
fn resolve(turns : & Turn, window : & Window, subjects : & dyn SubjectSource) -> Resolved |
fn reduce(resolved : & Resolved, denylist : & String) -> Option <ClassifierInput> |
ClassifierInput
History in, referents out — then mask what is left.
| Item |
|---|
pub struct ClassifierInput |
Resolved
History in, referents out — then mask what is left.
| Item |
|---|
pub struct Resolved |
SubjectSource
History in, referents out — then mask what is left.
| Item |
|---|
pub trait SubjectSource |
store (other)
Where a turn stops being a decision and becomes rows.
| Item |
|---|
async fn store_then_deliver(conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties, store : & dyn ThreadStore, delivery : & dyn Delivery,) -> Result <TurnOutcome, StoreError> |
async fn deliver_stored(stored : StoredTurn, store : & dyn ThreadStore, delivery : & dyn Delivery,) -> TurnOutcome |
Delivery
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub trait Delivery |
DeliveryError
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct DeliveryError |
DeliveryError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result |
StoreError
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct StoreError |
StoreError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result |
StoredMessage
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct StoredMessage |
StoredTurn
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct StoredTurn |
ThreadStore
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub trait ThreadStore |
TurnOutcome
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct TurnOutcome |
TurnParties
Where a turn stops being a decision and becomes rows.
| Item |
|---|
pub struct TurnParties |
TurnParties :: fn sender_for(& self, direction : Direction) -> Option <Uuid> |
thread (other)
What one turn puts in the thread.
| Item |
|---|
fn may_commit(computed_under : u64, current : u64) -> bool |
fn operator_note(handoff : & Handoff) -> String |
Direction
What one turn puts in the thread.
| Item |
|---|
pub enum Direction |
Direction :: fn as_str(self) -> & 'static str |
ThreadWrite
What one turn puts in the thread.
| Item |
|---|
pub struct ThreadWrite |
fn plan_writes(buyer_text : & str, outcome : & Outcome) -> Vec <ThreadWrite> |
window (other)
Which turns the classifier is allowed to see.
| Item |
|---|
fn needs_context(text : & str) -> bool |
Speaker
Which turns the classifier is allowed to see.
| Item |
|---|
pub enum Speaker |
Turn
Which turns the classifier is allowed to see.
| Item |
|---|
pub struct Turn |
Turn :: fn buyer(text : impl Into <String>) -> Self |
Turn :: fn operator(text : impl Into <String>) -> Self |
Window
Which turns the classifier is allowed to see.
| Item |
|---|
pub struct Window |
fn window(turns : & Turn, policy : WindowPolicy) -> Option <Window> |
WindowPolicy
Which turns the classifier is allowed to see.
| Item |
|---|
pub struct WindowPolicy |
WindowPolicy :: fn default() -> Self |
How to use it
From this crate's own rustdoc:
**Outbound (Story 3).** What the engine concluded becomes rows in the thread:
the buyer's turn, the reply, and — when the bot declines — one fixed phrase
for the buyer and a full account for the operator, on a conversation that is
assigned rather than merely annotated. Buttons are stored as capabilities the
server issued, and a tap redeems one by id.
Module structure
application_conversation
admitcatalogueclassifyguardofferpipelinepostgresreducestorethreadwindow
flowchart TD n_application_conversation["application_conversation"] n_application_conversation --> n_admit["admit"] n_application_conversation --> n_catalogue["catalogue"] n_application_conversation --> n_classify["classify"] n_application_conversation --> n_guard["guard"] n_application_conversation --> n_offer["offer"] n_application_conversation --> n_pipeline["pipeline"] n_application_conversation --> n_postgres["postgres"] n_application_conversation --> n_reduce["reduce"] n_application_conversation --> n_store["store"] n_application_conversation --> n_thread["thread"] n_application_conversation --> n_window["window"]
Public surface
`admit`
| Item | What it is |
|---|---|
pub struct RawCandidate | What a classifier returned, before anything has been checked. |
RawCandidate :: fn new(intent : impl Into <String>, score : f32) -> Self | — |
pub enum AdmitError | Why nothing usable survived admission |
fn admit(raw : & RawCandidate, registry : & IntentRegistry,) -> Result <Vec <Candidate>, AdmitError> | Check a classifier's output into the engine's input type |
`catalogue`
| Item | What it is |
|---|---|
pub struct Viewer | What the reader is allowed to be, for access purposes |
Viewer :: fn may_see(& self, tier : Visibility) -> bool | May this viewer see an item at tier? |
pub enum BuyerAvailability | What a buyer may be told about an item, in a buyer's vocabulary |
BuyerAvailability :: fn of(status : CatalogItemStatus) -> Option <Self> | The catalogue state a buyer may be told about, if any |
BuyerAvailability :: fn catalogue_key(self) -> & 'static str | The catalogue key for this word |
pub struct BuyerItemView<'a> | An item, already established as one this viewer may be told about. |
pub const DEFAULT_MAX_SLOT_LEN: usize | Slots longer than this refuse the render. |
BuyerItemView<'a> :: fn of(item : & 'a CatalogItem, viewer : & Viewer) -> Option <Self> | The only way to get a view |
BuyerItemView<'a> :: fn with_max_slot_len(mut self, max : usize) -> Self | Refuse a render past this length rather than truncating it. |
BuyerItemView<'a> :: fn slot_names() -> & 'static & 'static str | Every reader-facing slot name this view answers to |
BuyerItemView<'_> :: fn slot(& self, name : & str) -> Option <String> | — |
BuyerItemView<'_> :: fn subjects_in(& self, text : & str) -> Vec <String> | AC7 |
`classify`
| Item | What it is |
|---|---|
pub const DEFAULT_DEADLINE: Duration | How long one classification may take, end to end |
pub const DEFAULT_MAX_RESPONSE_BYTES: usize | The most answer this adapter will parse before refusing it |
pub struct CompletionClassifier | A Classifier backed by one completion per message |
CompletionClassifier :: fn fmt(& self, f : & mut std::fmt::Formatter <'_>) -> std::fmt::Result | — |
CompletionClassifier :: fn new(provider : Arc <dyn AiProvider>, registry : & IntentRegistry) -> Self | Build a classifier that answers in registry's vocabulary |
CompletionClassifier :: fn with_deadline(mut self, deadline : Duration) -> Self | Override the whole-turn deadline. |
CompletionClassifier :: fn with_max_response_bytes(mut self, bytes : usize) -> Self | Override the byte ceiling on an answer. |
CompletionClassifier :: fn with_model(mut self, model : impl Into <String>) -> Self | Name a model rather than taking the provider's default. |
CompletionClassifier :: fn system_prompt(& self) -> & str | The instruction the model is given, without the message |
CompletionClassifier :: async fn classify(& self, input : & ClassifierInput,) -> Result <Vec <RawCandidate>, ClassifierError> | — |
`guard`
| Item | What it is |
|---|---|
pub struct HoldFact | One hold on the window under discussion, as it was read. |
HoldFact :: fn consumes_capacity(& self, now : DateTime <Utc>) -> bool | Does this hold consume a unit of the window's capacity? Not the same question as `Hold::is_valid()`, and the difference is the one most likely to ship unnoticed |
HoldFact :: fn held_by(& self, party : & str, now : DateTime <Utc>) -> bool | Is this a live hold belonging to party? |
pub struct WindowSnapshot | What the conversation could see when it was read |
WindowSnapshot :: fn consumed(& self) -> u32 | How many units of the window are spoken for. |
WindowSnapshot :: fn permitted(& self) -> u32 | The most that may be consumed, given the pool's own policy. |
WindowSnapshot :: fn has_room(& self) -> bool | Is there room for one more, by the pool's rule rather than by whether a rival exists? "Someone else holds one" is the right question only for a capacity of one with overbooking off — one configuration, not the rule. |
WindowSnapshot :: fn party_holds(& self) -> bool | Does this conversation's party already hold a live unit? |
pub enum Requires | What an intent needs from the conversation's state. |
pub struct IntentPolicy | The intents this guard knows how to judge. |
IntentPolicy :: fn new() -> Self | — |
IntentPolicy :: fn with(mut self, intent : impl Into <String>, requires : Requires) -> Self | — |
IntentPolicy :: fn requirement(& self, intent : & str) -> Option <Requires> | — |
pub struct UnclassifiedIntents | An intent was registered that the guard has no policy for. |
pub const DEFAULT_MAX_SNAPSHOT_AGE: Duration | How stale a snapshot may be before a consequential intent is refused on that ground alone. |
pub struct ConversationGuard | The consumer's answer to the engine's question, from a snapshot. |
ConversationGuard :: fn new(snapshot : WindowSnapshot, policy : IntentPolicy, registered : & & str,) -> Result <Self, UnclassifiedIntents> | Build a guard, refusing to exist if any registered intent has no policy |
ConversationGuard :: fn with_max_age(mut self, max_age : Duration) -> Self | — |
ConversationGuard :: fn is_stale(& self, now : Instant) -> bool | Has this snapshot aged past the tolerance? Monotonic. |
ConversationGuard :: fn permits(& self, intent : & str) -> GuardVerdict | — |
`offer`
| Item | What it is |
|---|---|
pub struct OfferId | An opaque handle to one offered button |
OfferId :: fn new(id : impl Into <String>) -> Self | — |
OfferId :: fn as_str(& self) -> & str | — |
pub struct Offer | One button, as the server stored it when it offered it. |
pub struct Redemption | Where a tap arrived. |
pub enum OfferVerdict | Whether the offer may be redeemed here and now. |
fn verdict(offer : Option <& Offer>, at : & Redemption) -> OfferVerdict | Can this offer be redeemed by this tap? Pure |
fn buyer_handoff_text() -> & 'static str | The one thing a buyer is told when the bot hands off |
`pipeline`
| Item | What it is |
|---|---|
pub trait Classifier | Maps a reduced input to scored intent names |
pub enum ClassifierError | Why no answer was obtained, cut by repair rather than by cause |
ClassifierError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result | — |
pub enum Outcome | What one inbound turn produced. |
pub enum Handoff | Why a person is needed, and from where |
pub struct Inbound<'a> | Everything the inbound path needs that it does not own. |
async fn handle_message(turns : & Turn, cfg : & Inbound <'_>) -> Outcome | A typed message: window, resolve, reduce, guard, classify, admit, respond. |
pub trait OfferStore | Claims an offer, atomically, or says why it could not |
pub enum ClaimOutcome | What a claim attempt did. |
async fn handle_tap(offer_id : & OfferId, at : & Redemption, offers : & dyn OfferStore, templates : & Template, subject : & dyn SlotSource, transition_guard : & dyn TransitionGuard,) -> Outcome | A tapped button |
`postgres`
| Item | What it is |
|---|---|
pub struct PostgresThreadStore | A ThreadStore over a Postgres pool. |
PostgresThreadStore :: fn new(pool : PgPool) -> Self | — |
async fn store_turn_in(conn : & mut PgConnection, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties,) -> Result <StoredTurn, StoreError> | Store one turn on a caller-supplied connection |
async fn thread_rows(pool : & PgPool, conversation_id : Uuid,) -> Result <Vec <StoredMessage>, StoreError> | The conversation's rows, in the order they happened |
PostgresThreadStore :: async fn store_turn(& self, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties,) -> Result <StoredTurn, StoreError> | — |
PostgresThreadStore :: async fn mark_delivered(& self, message_id : Uuid) -> Result <(), StoreError> | — |
PostgresThreadStore :: async fn mark_delivery_failed(& self, message_id : Uuid, note : & str) -> Result <(), StoreError> | — |
pub struct NewOffer | One offer to store at a stated epoch and issue time |
async fn insert_offer(pool : & PgPool, offer : & NewOffer) -> Result <(), StoreError> | Mint one offer at a caller-stated epoch and issue time |
pub struct OfferTerms | What an offer needs that the reply cannot know |
async fn insert_offers_in(conn : & mut PgConnection, conversation_id : Uuid, writes : & ThreadWrite, stored : & StoredTurn, terms : & OfferTerms,) -> Result <Vec <OfferId>, StoreError> | Store one turn's buttons as capabilities, on the caller's connection |
pub enum TurnCommit | What became of a turn that was offered to the thread. |
async fn store_turn_and_offers(pool : & PgPool, conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties, terms : & OfferTerms, computed_under : Option <u64>,) -> Result <TurnCommit, StoreError> | One turn and its buttons, in one transaction, if the conversation has not moved under it |
async fn take_over(pool : & PgPool, conversation_id : Uuid, operator : Uuid,) -> Result <u64, StoreError> | Take a conversation for an operator: assign it and spend every capability the bot issued under the current epoch, in one single-row update |
pub struct PostgresOfferStore | An OfferStore over a Postgres pool. |
PostgresOfferStore :: fn new(pool : PgPool) -> Self | — |
PostgresOfferStore :: async fn claim(& self, id : & OfferId, at : & Redemption) -> ClaimOutcome | Lock the row, ask the pure verdict, consume only if it said so — one transaction |
`reduce`
| Item | What it is |
|---|---|
pub trait SubjectSource | The catalogue values this thread might be about |
pub struct Resolved | What survived the history. |
pub struct ClassifierInput | What the classifier is given. |
pub const SUBJECT_TOKEN: & str | The token a catalogue value is projected to before masking. |
fn resolve(turns : & Turn, window : & Window, subjects : & dyn SubjectSource) -> Resolved | Consume the context into referents |
fn reduce(resolved : & Resolved, denylist : & String) -> Option <ClassifierInput> | Project subjects to a typed token, then mask what is left |
`store`
| Item | What it is |
|---|---|
pub struct StoredMessage | One row as the store wrote it, carrying the durable id delivery needs. |
pub struct StoredTurn | What one stored turn returned. |
pub struct StoreError | The store could not do what was asked |
StoreError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result | — |
pub struct DeliveryError | The transport could not deliver |
DeliveryError :: fn fmt(& self, f : & mut fmt::Formatter <'_>) -> fmt::Result | — |
pub struct TurnParties | Who the rows of one turn are from |
TurnParties :: fn sender_for(& self, direction : Direction) -> Option <Uuid> | The sender for a row in this direction |
pub trait ThreadStore | Puts one turn in the thread, atomically |
pub trait Delivery | Gets a stored row to the reader it was written for |
pub struct TurnOutcome | One stored turn, and what happened to it after it was stored. |
async fn store_then_deliver(conversation_id : Uuid, writes : & ThreadWrite, operator : Option <Uuid>, parties : & TurnParties, store : & dyn ThreadStore, delivery : & dyn Delivery,) -> Result <TurnOutcome, StoreError> | Store the turn, then deliver what the reader is meant to see |
async fn deliver_stored(stored : StoredTurn, store : & dyn ThreadStore, delivery : & dyn Delivery,) -> TurnOutcome | Deliver a turn that is already stored |
`thread`
| Item | What it is |
|---|---|
pub enum Direction | Who a row is from, in the store's own vocabulary. |
Direction :: fn as_str(self) -> & 'static str | — |
pub struct ThreadWrite | One row this turn puts in the thread. |
fn plan_writes(buyer_text : & str, outcome : & Outcome) -> Vec <ThreadWrite> | What the thread records for one inbound turn and its outcome |
fn may_commit(computed_under : u64, current : u64) -> bool | May a reply computed under computed_under still be written? AC6, and the reason it is a check at commit rather than a check before computing: the operator can take the conversation while a reply is already in flight |
fn operator_note(handoff : & Handoff) -> String | The operator-facing description of an abstention |
`window`
| Item | What it is |
|---|---|
pub enum Speaker | — |
pub struct Turn | One turn of a thread. |
Turn :: fn buyer(text : impl Into <String>) -> Self | — |
Turn :: fn operator(text : impl Into <String>) -> Self | — |
pub struct WindowPolicy | How far back the search may go before it gives up |
WindowPolicy :: fn default() -> Self | — |
pub struct Window | The turns the classifier may see, as indices into the thread. |
fn needs_context(text : & str) -> bool | Can this turn be read on its own? False means it depends on something said earlier |
fn window(turns : & Turn, policy : WindowPolicy) -> Option <Window> | The window for a thread: the last buyer turn, plus the preceding turns it needs, bounded |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
CompletionClassifier | classify::CompletionClassifier |
{admit,AdmitError,RawCandidate} | admit::{admit,AdmitError,RawCandidate} |
{buyer_handoff_text,verdict,Offer,OfferId,OfferVerdict,Redemption} | offer::{buyer_handoff_text,verdict,Offer,OfferId,OfferVerdict,Redemption} |
{deliver_stored,store_then_deliver,Delivery,DeliveryError,StoreError,StoredMessage,StoredTurn,ThreadStore,TurnOutcome,TurnParties,} | store::{deliver_stored,store_then_deliver,Delivery,DeliveryError,StoreError,StoredMessage,StoredTurn,ThreadStore,TurnOutcome,TurnParties,} |
{handle_message,handle_tap,ClaimOutcome,Classifier,ClassifierError,Handoff,Inbound,OfferStore,Outcome,} | pipeline::{handle_message,handle_tap,ClaimOutcome,Classifier,ClassifierError,Handoff,Inbound,OfferStore,Outcome,} |
{needs_context,window,Speaker,Turn,Window,WindowPolicy} | window::{needs_context,window,Speaker,Turn,Window,WindowPolicy} |
{operator_note,plan_writes,Direction,ThreadWrite} | thread::{operator_note,plan_writes,Direction,ThreadWrite} |
{reduce,resolve,ClassifierInput,Resolved,SubjectSource,SUBJECT_TOKEN} | reduce::{reduce,resolve,ClassifierInput,Resolved,SubjectSource,SUBJECT_TOKEN} |
Boundary
Reaches into domain, identity, infrastructure.
Shares tier application with 120 other crates: application-agreements, application-ai, application-analytics, application-approvals, application-assessments, application-audit-log, application-auth, application-billing, … (120 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | application |
| Architectural role (taxonomy) | pipeline |
| Location | crates/application/conversation |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_application["application"] --> n_domain["domain"] n_application["application"] --> n_identity["identity"] n_application["application"] --> n_infrastructure["infrastructure"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `domain-catalog` | domain | no | always |
| `domain-conversation-engine` | domain | no | always |
| `domain-reservations` | domain | no | always |
| `identity-parties` | identity | yes | always |
| `infrastructure-agent` | infrastructure | no | always |
| `infrastructure-ai` | infrastructure | yes | always |
| `infrastructure-communication` | infrastructure | yes | always |
| `infrastructure-dlp-detect` | infrastructure | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | yes | always |
serde_json | ^1 | — | yes | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | yes | always |
tokio | ^1 | full | yes | always |
uuid | ^1 | v4, v7, serde, js | no | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `foundation-money` | foundation | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
dotenvy | ^0.15 | — | no | always |
rust_decimal | ^1 | serde | no | always |
tokio | ^1 | full, macros, rt, rt-multi-thread | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["application-conversation"] SELF -->|development| n_foundation_money["foundation-money"] SELF -->|runtime| n_domain_catalog["domain-catalog"] SELF -->|runtime| n_domain_conversation_engine["domain-conversation-engine"] SELF -->|runtime| n_domain_reservations["domain-reservations"] SELF -->|runtime| n_identity_parties["identity-parties"] SELF -->|runtime| n_infrastructure_agent["infrastructure-agent"] SELF -->|runtime| n_infrastructure_ai["infrastructure-ai"] SELF -->|runtime| n_infrastructure_communication["infrastructure-communication"] SELF -->|runtime| n_infrastructure_dlp_detect["infrastructure-dlp-detect"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
| Feature | Enables | On by default |
|---|---|---|
completion | dep:infrastructure-ai, dep:tokio, dep:serde, dep:serde_json | no |
default | — | yes |
postgres | dep:sqlx, dep:serde_json, dep:infrastructure-communication, dep:identity-parties | no |
flowchart LR n_completion["completion"] --> n_dep_infrastructure_ai["dep:infrastructure-ai"] n_completion["completion"] --> n_dep_tokio["dep:tokio"] n_completion["completion"] --> n_dep_serde["dep:serde"] n_completion["completion"] --> n_dep_serde_json["dep:serde_json"] n_default["default"] n_postgres["postgres"] --> n_dep_sqlx["dep:sqlx"] n_postgres["postgres"] --> n_dep_serde_json["dep:serde_json"] n_postgres["postgres"] --> n_dep_infrastructure_communication["dep:infrastructure-communication"] n_postgres["postgres"] --> n_dep_identity_parties["dep:identity-parties"]
Targets
| Kind | Name | Source |
|---|---|---|
| lib | application_conversation | `src/lib.rs` |
| test | pg_end_to_end | `tests/pg_end_to_end.rs` |
| test | pg_offer_claim | `tests/pg_offer_claim.rs` |
| test | pg_thread_store | `tests/pg_thread_store.rs` |
Error model
| Error type | Named by |
|---|---|
AdmitError | admit |
ClassifierError | declared, no public signature returns it |
DeliveryError | declared, no public signature returns it |
StoreError | insert_offer, insert_offers_in, store_then_deliver, store_turn_and_offers, store_turn_in, take_over, … (7 total) |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | yes |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 125 |
| Integration tests | 24 |
| Examples | 0 |
| Doctests | 1 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
admit | 3 | 0 | 0 |
catalogue | 4 | 0 | 0 |
classify | 3 | 0 | 0 |
guard | 7 | 0 | 0 |
offer | 6 | 0 | 0 |
pipeline | 9 | 0 | 0 |
postgres | 11 | 0 | 0 |
reduce | 6 | 0 | 0 |
store | 10 | 0 | 0 |
thread | 5 | 0 | 0 |
window | 6 | 0 | 0 |
What the tests establish, by name:
a_button_minted_after_a_takeover_carries_the_epoch_the_conversation_is_on—tests/pg_end_to_end.rsa_buyer_sentence_becomes_a_stored_reply_a_delivered_one_and_a_claimable_button—tests/pg_end_to_end.rsa_refused_button_takes_the_whole_turn_with_it—tests/pg_end_to_end.rsa_reply_computed_before_a_takeover_does_not_commit_after_it—tests/pg_end_to_end.rsa_transport_failure_is_a_stored_handoff_that_says_which_failure_it_was—tests/pg_end_to_end.rsa_turn_that_asserts_no_epoch_commits_after_a_takeover—tests/pg_end_to_end.rsa_claim_returns_the_intent_the_server_stored—tests/pg_offer_claim.rsa_takeover_spends_every_offer_issued_before_it—tests/pg_offer_claim.rsten_taps_still_one_claim—tests/pg_offer_claim.rsthe_adapter_refuses_for_the_same_reasons_the_pure_verdict_does—tests/pg_offer_claim.rstwo_taps_one_claim—tests/pg_offer_claim.rsa_delivered_row_says_delivered—tests/pg_thread_store.rsa_delivery_failure_leaves_a_stored_row_that_says_so—tests/pg_thread_store.rsa_rolled_back_turn_leaves_no_rows—tests/pg_thread_store.rsa_stored_turn_records_who_each_row_is_from—tests/pg_thread_store.rsan_abstention_assigns_the_conversation—tests/pg_thread_store.rsan_abstention_with_no_operator_is_stored_unassigned—tests/pg_thread_store.rsan_answered_turn_assigns_nothing—tests/pg_thread_store.rsan_assigned_abstention_does_not_report_itself_unassigned—tests/pg_thread_store.rsanother_producers_row_sorts_rather_than_raising—tests/pg_thread_store.rsrows_sharing_one_instant_are_ordered_by_their_place_in_the_turn—tests/pg_thread_store.rsthe_stored_instant_is_not_constant_across_a_turn—tests/pg_thread_store.rsthree_rows_of_one_turn_read_back_in_the_order_they_happened—tests/pg_thread_store.rstwo_turns_on_one_conversation_do_not_interleave—tests/pg_thread_store.rsa_duplicated_intent_collapses_to_its_highest_score—src/admit.rsa_partly_usable_result_is_admitted—src/admit.rsa_score_outside_zero_to_one_is_rejected—src/admit.rsa_score_that_is_not_a_number_is_rejected—src/admit.rsan_unregistered_intent_is_rejected—src/admit.rsnothing_offered_is_distinct_from_nothing_admissible—src/admit.rs- _… 119 more_
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 91 | 115 |
Public modules with a //! block | 11 | 11 |
pie showData
title Public items with rustdoc
"Documented" : 91
"No rustdoc detected" : 24
Metrics
| Metric | Value |
|---|---|
| Rust source files | 12 |
| Source lines | 6003 |
| Code lines | 4008 |
| Public API items | 115 |
| Public modules | 11 |
| Tests | 149 |
| Examples | 0 |
| Cargo features | 3 |
| Direct runtime dependencies | 15 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 5
"enum" : 11
"function" : 20
"method" : 45
"struct" : 29
"trait" : 5
pie showData
title Rust source composition
"Code" : 4008
"Blank or comment" : 1995
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.