How a conversation is allowed to end: the closed set of intents a member may pick, which of them end a conversation and which only defer it, and a decline type with nowhere to put a member's own words.
| Tier | foundation |
| Role | value_object |
| Path | crates/foundation/conversation-closure |
| Edition | 2021 |
| Targets | foundation_conversation_closure |
| Public items | 10 across 0 modules |
| Tests | 8 |
What it is for
How a conversation is allowed to end.
Built before the discovery grid, on the Gate 1.5 panel's finding (2026-08-27, unanimous): prove the closure mechanism first and add matching once it works. A grid that surfaces people is worth nothing if the ending it exists to enable does not hold its shape.
# The mechanism is the absence of a field
The product promise is that a decline is a courtesy from the application, never ghost-written in a member's voice. Every seat in the consensus round landed independently on the same enforcement, and it is not a review rule or a lint:
Decline carries an Intent and a TemplateVersion. There is no field for a member's words, no field for tone, and no field for style. "Make it sound more like me" cannot be added as an option because there is nowhere to put it — it would require changing this type, which is a visible act with a test attached rather than a tempting one-line addition to a request struct.
no_member_words_can_reach_the_recipient is that test. It is not a formality: it exists so that the day someone adds a String to this type in good faith, the build says no and names the promise being broken.
# What this crate does NOT do
It does not generate text. The wording lives in a reviewed catalogue keyed by (intent, template_version) on the application side, so that changing what people read is a content review rather than a code change, and so that every message ever sent can be reconstructed from two stored values.
It does not classify anything. Members pick an intent; nothing infers one. Where a model is involved at all it is offline, proposing catalogue variants for review, and its output becomes static data before it can reach anyone.
It does not re-implement domain-conversation-engine's IntentRegistry or application-conversation's admit, which already enforce that unconstrained model output never becomes a Candidate (sprint 3.114, 99 tests). Those govern classifying an incoming message; this governs a member choosing how to end one, and the choosing step has no model in it at all.
Capabilities
Decline
How a conversation is allowed to end.
| Item |
|---|
pub struct Decline |
Decline :: fn new(intent : Intent, template : TemplateVersion) -> Self |
Decline :: fn closes_the_conversation(self) -> bool |
fn endings(declines : & Decline) -> usize |
Intent
How a conversation is allowed to end.
| Item |
|---|
pub struct Intent |
Intent :: fn is_terminal(self) -> bool |
fn vocabulary() -> Vec <Intent> |
fn intent(token : & str) -> Option <Intent> |
Kind
How a conversation is allowed to end.
| Item |
|---|
pub enum Kind |
TemplateVersion
How a conversation is allowed to end.
| Item |
|---|
pub struct TemplateVersion |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
`crate root`
| Item | What it is |
|---|---|
pub enum Kind | Whether an intent ends a conversation or only pauses it |
pub struct Intent | One intent a member may pick. |
Intent :: fn is_terminal(self) -> bool | Does choosing this end the conversation? |
pub struct TemplateVersion | Which reviewed wording a member's counterpart was shown |
pub struct Decline | A member's decision to end or pause a conversation |
Decline :: fn new(intent : Intent, template : TemplateVersion) -> Self | Record a decline |
Decline :: fn closes_the_conversation(self) -> bool | Does this close the conversation for the purpose of the closure measure? |
fn vocabulary() -> Vec <Intent> | Every intent in the ratified vocabulary, in the seed's order |
fn intent(token : & str) -> Option <Intent> | Look up one intent |
fn endings(declines : & Decline) -> usize | The closure measure's numerator: declines that actually ended something |
No pub use re-exports: every item above is declared in this crate.
Boundary
Depends on no other workspace tier.
Shares tier foundation with 27 other crates: foundation-audit-log, foundation-basemodels, foundation-bounded-io, foundation-crypto-sign, foundation-decisioning, foundation-encounter-vocabulary, foundation-fs-metadata, foundation-i18n, … (27 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) | foundation |
| Architectural role (taxonomy) | value_object |
| Location | crates/foundation/conversation-closure |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime. None.
Development. None.
Build. None.
Depended on by. Nothing in this workspace.
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| lib | foundation_conversation_closure | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| 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 | 8 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 7 | 0 | 0 |
What the tests establish, by name:
an_unknown_token_cannot_be_picked—src/lib.rsbusy_right_now_is_a_defer_too—src/lib.rsno_member_words_can_reach_the_recipient—src/lib.rsremind_me_later_is_a_defer_and_not_an_ending—src/lib.rsthe_measure_counts_endings_and_not_declines_issued—src/lib.rsthe_plain_declines_are_terminal—src/lib.rsthe_seed_carries_every_intent_scope_names—src/lib.rswhat_a_recipient_reads_is_reconstructible_from_what_is_stored—src/lib.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 10 | 10 |
Public modules with a //! block | 0 | 0 |
pie showData
title Public items with rustdoc
"Documented" : 10
"No rustdoc detected" : 0
Metrics
| Metric | Value |
|---|---|
| Rust source files | 1 |
| Source lines | 265 |
| Code lines | 141 |
| Public API items | 10 |
| Public modules | 0 |
| Tests | 8 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 0 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"enum" : 1
"function" : 3
"method" : 3
"struct" : 3
pie showData
title Rust source composition
"Code" : 141
"Blank or comment" : 124
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.