#[non_exhaustive]pub enum NarratableFact {
Show 18 variants
OperationAvailable {
workflow: WorkflowKey,
operation: OperationKey,
summary: String,
},
Record {
case_ref: CaseRef,
label: Option<String>,
phase: Value,
outcome: Option<Value>,
},
StateValue {
case_ref: CaseRef,
field: String,
value: Value,
},
ProposedChange {
case_ref: CaseRef,
field: String,
before: FieldValue,
after: FieldValue,
},
OperationalOutcome {
receipt_id: ReceiptId,
event_ids: Vec<EventId>,
status_code: String,
},
InteractionAvailable {
interaction_id: InteractionId,
interaction_kind: InteractionKind,
},
InstructionDeclined {
case_ref: CaseRef,
interaction_id: InteractionId,
option_id: OptionId,
question: String,
option_label: String,
},
ActAwaitingConfirmation {
case_ref: CaseRef,
operation: String,
subject: Option<String>,
},
ActRefused {
case_ref: Option<CaseRef>,
code: String,
explanation: String,
},
ActChangedNothing {
case_ref: CaseRef,
operation: Option<String>,
explanation: String,
},
ValueNeeded {
case_ref: Option<CaseRef>,
operation: String,
arguments: Vec<String>,
reason: Option<String>,
},
NotUnderstood {
words: String,
},
ActHeld {
operation: String,
because: String,
},
WorkflowUnavailable {
workflow: WorkflowKey,
reason: String,
},
InteractionAlreadyAnswered {
interaction_id: InteractionId,
option_id: Option<OptionId>,
progress: AnswerProgress,
},
AttachmentNotShown {
attachment_id: AttachmentId,
filename: Option<String>,
reason: String,
},
ObligationOpen {
case_ref: CaseRef,
obligation: Value,
sentence: Option<String>,
relevance: FactRelevance,
},
Knowledge {
chunk_id: String,
source_id: String,
text: String,
},
}Expand description
A fact the narrator may state (spec §18.3). Everything else is forbidden.
New fact kinds are expected, so downstream matches need a wildcard arm.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
OperationAvailable
Something the user can do now, in the workflow’s words.
Record
A record a question is about: its name, where its lifecycle stands, and its outcome once complete.
Fields
StateValue
A committed field value.
ProposedChange
A proposed, uncommitted change.
Fields
before: FieldValueCurrent value.
after: FieldValueProposed value. FieldValue::cleared is “the field is being
emptied”, which is not the same statement as “unchanged”.
OperationalOutcome
An operational outcome; must be backed by a receipt block.
Fields
InteractionAvailable
A card is available; must be backed by an interaction block.
InstructionDeclined
The user declined an option on a card, so the server did nothing.
§Why this is not Self::ActRefused
The two look alike and read differently, which is the whole point. A refusal is the server saying no and owes the user a reason. A decline is the user saying no and owes them an acknowledgement — “all right, I haven’t; tell me what you’d like to change” rather than “that could not be done”. Folding them together would hand the narrator one word for two situations and it would write the wrong one half the time.
§Why the narrator needs it at all
The deterministic notice already says the instruction was declined, and it says it whether or not a model runs. What it cannot do is say it in the register of the conversation. Without this fact the narrator sees a turn with no plan, no receipts and nothing outstanding, and the only sentence that fits an empty brief is a generic offer of help — so the user reads a non-sequitur above a canned line, and it is obvious which of the two a machine wrote.
Fields
interaction_id: InteractionIdThe card that was answered.
ActAwaitingConfirmation
An act this turn prepared and did not run, because a card is asking the user to authorize it first.
Forbidding the claim that it happened is not enough: a writer told it may not assert X, with no fact to say instead, asserts not-X. This is that fact, so the true sentence («before I do it, I am asking you») is the one it can write. It is emitted wherever the confirmation policy defers a command, since a card raised by policy moves no case and no phase or briefing knows it is there.
Fields
ActRefused
An act the domain refused, and why.
§Why a refusal is not a claim
The claim guard exists to stop a model saying something happened that did not. Saying that something did not happen, with the reason the server itself produced, does not cross that line — it is the honest half of the same rule. Without it an assistant forbidden from claiming success has no way to report failure either, so it writes about something else and the user concludes the write went through.
That is not hypothetical: it is what happens next. A user told nothing about a refusal says “I already gave you that”, the interpreter reads a transcript in which the refusal never occurred, and the complaint gets written into the field the refusal was protecting.
Fields
case_ref: Option<CaseRef>The case the act was aimed at, when its target resolved to one.
Absent when the refusal is the target: an act naming a shape the operation does not accept, or a card that is not on screen, never reached a case at all. The narrator is told about it anyway, because the user asked for something and did not get it.
ActChangedNothing
An act the domain accepted, and that changed nothing.
Not a refusal: nothing about it was wrong. The state it asks for is the state the case is already in, so the workflow compiled no commands — which is a legitimate answer, and the one a singleton workflow relies on when its start is proposed a second time.
§Why the narrator needs it
For the reason Self::ActRefused gives, and it is the same sentence:
an outcome nobody is told about is read by the user as a success, and by
the next turn’s interpreter as a transcript in which it never happened.
A refusal has been told for a long time; this one was not, and it leaves
exactly the same hole — no receipt, no fact, obligations unchanged — so
the writing stage does the only thing an empty brief allows. It asks for
something else, or it announces the write.
One turn paid for it whole. The user asked to go on with a draft; the plan proposed starting a workflow that was already open; the domain compiled nothing, the act left no trace, and the reply invented a datum on a document the user had never named.
Fields
operation: Option<String>The operation, as the catalogue names it. Absent for the acts that carry none — a start is the common one.
explanation: StringThe workflow’s own sentence saying WHY nothing changed, in the reader’s locale, or empty when it wrote none.
One workflow compiles nothing for reasons that want opposite replies — the singleton whose start was proposed twice, the write that tells a field what it already says — and the name of the operation does not separate them. A writer given only the name invents, and what it reaches for is a refusal: «I cannot do that here», to a user who had asked for a correction and now has no idea what to say next.
Filled from
WorkflowDefinition::nothing_changed;
empty is what every workflow said before that channel existed, and
it defaults on deserialization so records written then still load.
ValueNeeded
An act that waits for values the user has not given: they are asked for, and nothing is written until they are.
Fields
NotUnderstood
Words of the message that produced nothing to act on.
ActHeld
An act held back because another part of the message about the same record was not understood.
A workflow this account cannot start yet, and the reason it declared.
Shown what is unavailable, the model rightly proposes no act, so there is no
refusal to carry the reason: it travels as this fact instead, like an open
obligation, something that has not happened, known from a declaration. It widens
nothing the narrator may claim: no ClaimClass covers it and no receipt backs it.
InteractionAlreadyAnswered
The card this turn answered had already been answered.
§The turn this fills
A second click on the same card authorizes nothing: the compare-and-set that guarantees it is the part of this worth leaving alone. But the record it comes back with used to be dropped, so from that point on nothing could tell a second click from a turn with no click at all — and a turn with no plan, no receipts and nothing outstanding still calls the stage that writes the lead-in, which does what that stage always does with an empty brief: it invents.
One such turn told a user to start their invoicing configuration from the payment method. It was collected, it was on the card they were looking at, and the case’s own guidance for that phase said not to start over.
Skipping the stage is not the fix, because the turn would go out silent and a second click is somebody who did not understand the first answer. The fix is to give the turn something true to say.
§It claims nothing this turn did
The original resolution’s events are surfaced beside this as receipts, so
prose that says the work is done is backed by the events that did it —
which is what “repeat the result without repeating the effect” means. On
a resolution still running there are no events yet, and
AnswerProgress::Running is the fact that keeps the prose off a claim
nothing backs.
Fields
interaction_id: InteractionIdThe card that was clicked again.
progress: AnswerProgressHow far the first answer got.
AttachmentNotShown
A file the turn carried that the model was not shown.
§Why the writing stage has to be told
A turn’s attachments reach the model as parts of the request, so a question about a document is answered from the document. When one does not get there — the application no longer holds it, the budget for one request would not take it, no provider in the chain accepts its media type — the model has the user’s sentence about a file and no file.
A stage asked to answer about something it cannot see answers anyway, and an answer about the wrong document is worse than an answer about none. So this is a fact and not only a notice: the notice tells the user deterministically, and the fact is what stops the prose describing a document nobody looked at.
It widens nothing the narrator may claim. A file not shown is not an
effect, so no ClaimClass covers it and no receipt could back it.
Fields
attachment_id: AttachmentIdThe file, as the turn named it.
ObligationOpen
An obligation the case still has open.
§Why this is a fact and not a licence
Everything else here describes something that happened. This describes something that has not: a value the workflow’s own projection says is still outstanding. It is on the same side of the design as the rest, because it comes from the projection rather than from the model, and the projection is a pure function of committed state.
It widens nothing the narrator may claim. An obligation is not an
effect, so no ClaimClass covers it and no receipt could back it; the
claim guard is unmoved. What it changes is whether the assistant can ask
for the next thing. A flow whose job is to collect several values could
previously only acknowledge each one and wait, because the stage that
writes the prose was never told what was still missing.
§It must be the obligations after the turn
The projection that framed the interpretation is the one from before the commands ran. Narrating from it asks the user for the value they have just supplied, which is worse than saying nothing. The runtime re-projects the cases a turn changed and narrates from that.
Fields
sentence: Option<String>The same thing in words a person would recognise, when the workflow
says it (WorkflowDefinition::obligation_sentence).
The serialized value is the domain’s structure, and a stage handed only that
guesses what to ask for.
relevance: FactRelevanceWhether this is a case the turn engaged or one that merely exists.
See FactRelevance. Facts about the turn sort first, because the
writing stage is told to ask for the first one.
Knowledge
Retrieved knowledge.
Trait Implementations§
Source§impl Clone for NarratableFact
impl Clone for NarratableFact
Source§impl Debug for NarratableFact
impl Debug for NarratableFact
Source§impl<'de> Deserialize<'de> for NarratableFact
impl<'de> Deserialize<'de> for NarratableFact
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for NarratableFact
Source§impl PartialEq for NarratableFact
impl PartialEq for NarratableFact
Source§impl Serialize for NarratableFact
impl Serialize for NarratableFact
impl StructuralPartialEq for NarratableFact
Auto Trait Implementations§
impl Freeze for NarratableFact
impl RefUnwindSafe for NarratableFact
impl Send for NarratableFact
impl Sync for NarratableFact
impl Unpin for NarratableFact
impl UnsafeUnpin for NarratableFact
impl UnwindSafe for NarratableFact
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,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.