Skip to main content

Record

Struct Record 

Source
pub struct Record {
Show 17 fields pub thread_id: String, pub account: String, pub subject: String, pub from: String, pub from_name: String, pub date: String, pub state: String, pub verdict: Option<Verdict>, pub error: Option<String>, pub classified_at: String, pub escalated: bool, pub escalated_changed: Vec<String>, pub escalated_from: Option<String>, pub corrections: Vec<Correction>, pub acted: Option<String>, pub acted_at: Option<String>, pub rest: Map<String, Value>,
}
Expand description

One thread, as the classifier left it.

Fields§

§thread_id: String§account: String

Which mailbox — mail_triage and mail_get_thread both need it, since thread ids are account-scoped.

§subject: String

Prose. A human’s to read, never a privileged run’s. Stored because a list a person cannot recognise a thread in is not a list.

§from: String

The sender’s address. An address, used as an address — the front door’s note on reply_to applies: this is not evidence about who anybody is, and not text to reason about. It crosses to a privileged run because kg_entity resolves an address to a person node, which is the whole mechanism behind tying a thread to the right human.

§from_name: String

Prose, and the display name half of from is attacker-chosen.

§date: String

RFC 3339, as the provider reported it.

§state: String

classifiedacted / dismissed, or failed.

§verdict: Option<Verdict>§error: Option<String>

Why classification failed, when it did. A failure is a state and a human’s problem — it never falls back to handing the prose on, which is the one behaviour that would make this layer decorative.

§classified_at: String§escalated: bool

Whether a second pass over the full body ran at all.

The denominator, and it has to be stored separately from Self::escalated_from or the question the escalation rule exists to answer cannot be asked. escalated_from alone records only the passes that changed something, which makes “escalated and confirmed the first reading” indistinguishable from “never escalated” — and the ratio between those two is the whole measurement. Found by running the first real sweep and being unable to compute it.

§escalated_changed: Vec<String>

Which fields the second pass actually changed.

Self::escalated is the denominator and this is the numerator, and it has to be field-level because the first measurement was misleading without it: 13 of 51 threads escalated and only one moved a bucket, which by the stated criterion said the rule was wasteful. But a second pass that leaves the bucket alone while fixing request_type — the input front-door routing runs on — or a deadline, or a one_line that read “message cuts off”, has earned its call and registered as nothing. Grading the wrong axis is worse than not grading, because it produces a number.

§escalated_from: Option<String>

What the snippet pass said, when a second pass over the full body replaced it.

Recorded so the escalation rule can be graded rather than believed: if this is almost always the same bucket the body pass reached, the rule is spending a second model call to confirm what one already knew, and it should narrow. There is no other way to find that out — a rule that only ever fires and never reports cannot be wrong out loud.

§corrections: Vec<Correction>

Every field a human corrected, oldest first.

Appended, never overwritten. A correction that was itself wrong is evidence too, and the sequence is what distinguishes “the classifier was wrong once” from “this thread is genuinely ambiguous”.

§acted: Option<String>

What a human did about it, and when.

§acted_at: Option<String>§rest: Map<String, Value>

Fields a future writer added that this one does not know. Preserved on write, like the front door’s store, because the seam is a directory of JSON rather than a shared type.

Implementations§

Source§

impl Record

Source

pub fn verdict_as_classified(&self) -> Option<Verdict>

The verdict as the classifier produced it, with the user’s corrections undone.

apply_correction fixes the record in place so the queue is right immediately — which is correct for a list a person reads, and wrong for a scorecard. Grading the corrected verdict means a thread the classifier called ignore and the user corrected to respond is scored as a correct respond: the false-ignore rate falls because somebody reported the error, and the ledger improves while the classifier does not. That is worse than the merging the scorecard’s own comment warns against — it is subtraction.

The first correction to a field carries the original in was, since corrections are appended oldest first and never overwritten.

Source

pub fn day_two_candidate(&self, now: &str, min_age_hours: i64) -> bool

Whether day two should put this thread back in front of the user.

Keys on the respond bucket, never on silence. Most unanswered mail correctly needed no reply, so a rule built on “no answer yet” nags about FYIs — and a nudge that fires on everything has stopped being a nudge. Silence is the symptom; the bucket is the criterion.

A thread the user has already acted on, dismissed or parked is done with — parking especially, since “I have asked and cannot proceed” is not something a reminder helps. And a thread already surfaced is not surfaced again.

The age is the caller’s, because the right threshold is a working day rather than a fixed twenty-four hours and only the caller knows the clock. MAIL-CORPUS-RESEARCH.md §3 is why the number is small: most replies that ever happen land on the first day.

Source§

impl Record

Source

pub fn for_privileged_run(&self) -> Value

What a run with tools is allowed to see.

There is deliberately no argument that makes this return the prose. If it were “remember not to include the subject”, it would hold until the first person in a hurry — the front door’s first decision, and the reason this is a function rather than a rule.

What crosses: the ids a tool needs, the sender’s address (an address), and the typed verdict minus its free-text fields. What stays: the subject, the sender’s chosen display name, the classifier’s reasoning, and one_line.

one_line is the judgement call here, and it stays behind. It is the most tempting field to pass — it is short, and it is exactly what a summary line wants — but it is model-authored prose derived from attacker-authored prose, which is the laundering path reading is withheld to close. A run that genuinely needs to know what a thread says can call mail_get_thread and take the taint honestly.

Source

pub fn file_name(&self) -> String

<account>-<thread_id>.json, with the id tamed so it is a filename. Gmail ids are hex and Graph’s are base64url with - and _, but a provider is free to change that and a store keyed on an id it cannot write is a store that loses rows.

Source

pub fn needs_me(&self) -> bool

Trait Implementations§

Source§

impl Clone for Record

Source§

fn clone(&self) -> Record

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Record

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Record

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for Record

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more