pub struct MessagingOrchestrator { /* private fields */ }Expand description
In-process iMessage adapter (re-homed from #403’s orchestrator). One per
daemon; held behind an Arc and driven by its InboundChannel::run() poll
loop. Cheap to clone the Arc. The channel-agnostic approval semantics live
in ApprovalCore; this struct owns the iMessage-specific transport
(chat.db poll, CodeMap text-code correlation, parse grammar).
Implementations§
Source§impl MessagingOrchestrator
impl MessagingOrchestrator
Sourcepub fn new(
host: Arc<HostState>,
config: MessagingConfigStore,
sender: Arc<dyn MessageSender>,
base_dir: impl Into<PathBuf>,
) -> MessagingOrchestrator
pub fn new( host: Arc<HostState>, config: MessagingConfigStore, sender: Arc<dyn MessageSender>, base_dir: impl Into<PathBuf>, ) -> MessagingOrchestrator
Build an orchestrator over the given host, config store, send seam, and
~/.car/-equivalent base dir. Production passes
RealMessageSender/MessagingConfigStore::from_home()/~/.car; tests
pass a spy sender and a temp base dir.
Sourcepub fn with_liveness(
host: Arc<HostState>,
config: MessagingConfigStore,
sender: Arc<dyn MessageSender>,
base_dir: impl Into<PathBuf>,
liveness: Arc<Mutex<HashMap<ChannelId, ChannelLiveness>>>,
) -> MessagingOrchestrator
pub fn with_liveness( host: Arc<HostState>, config: MessagingConfigStore, sender: Arc<dyn MessageSender>, base_dir: impl Into<PathBuf>, liveness: Arc<Mutex<HashMap<ChannelId, ChannelLiveness>>>, ) -> MessagingOrchestrator
Build an orchestrator that ALSO records every outbound send’s outcome
into the shared per-channel liveness map (U2/U3). The boot path and the
runtime-enable spawn closure use this so messaging.status sees real
“last delivered” + last-error state; the messaging.test_send self-test
(U4) relies on the same recording so a test failure surfaces identically
to a real one.
Sourcepub fn parse_call_count(&self) -> u64
pub fn parse_call_count(&self) -> u64
Test/diagnostic accessor: how many inbound bodies reached the parser. SC-7 asserts this is 0 after a non-allowlisted row is fed.
Sourcepub async fn observe_and_notify(&self)
pub async fn observe_and_notify(&self)
Observe new eligible approvals on HostState and send ONE iMessage
prompt per newly-seen one. Idempotent per approval: an approval we have
already minted a code for is skipped, so calling this every tick sends
at most one prompt per approval.
Gating (the enabled-flag wall): if the feature is disabled OR there is
no paired handle, this is a silent no-op — zero sends. Excluded rows:
any ws.method:* blocking-gate row (wrong producer) and any
already-resolved row.
The single paired recipient is the FIRST allowlisted handle (v1 is one paired/allowlisted user — ledger item 2). A send failure is logged and does NOT poison the loop; the code stays mapped so the inbound reply still correlates if the message did go through.
Fan-out outbound (Unit 5): send the iMessage prompt for approval using
a SHARED code minted ONCE by the fan-out coordinator (not this
adapter’s CodeMap.mint), so iMessage and Slack carry the SAME code
(MC-8). Records code ↔ approval_id into this adapter’s CodeMap so the
inbound text-resolve path still correlates a reply. Idempotent per
approval: an approval already mapped here is skipped (no duplicate send).
A send failure rolls back the mapping (same reliability contract as
observe_and_notify).
The rendered body is byte-for-byte the same outbound_body the iMessage
poll path uses — only the code SOURCE differs (shared vs self-minted), so
the iMessage grammar/behavior is unchanged (MC-1). Gating (enabled flag +
paired handle) is applied here too.
Sourcepub async fn handle_inbound(&self, msg: &InboundMessage)
pub async fn handle_inbound(&self, msg: &InboundMessage)
Handle one inbound row. SC-7: a non-allowlisted sender is dropped
BEFORE any parse (the parse counter does not move). Then the body is
parsed to one InboundIntent and mapped:
<code> approve|deny→ resolve THAT approval id (if it is a known, still-pending, eligible approval).- bare
approve|deny→ resolve the SOLE pending eligible approval; if 2+ are pending, resolve nothing and send one disambiguation reply. (v1 has one paired handle — see the module-level single-handle invariant — so “the sole pending approval” needs no per-sender scope.) - unknown / already-resolved code → resolve nothing.
- pairing code → validate-and-consume (the ONLY inbound-reachable config mutation; binds the sender only on a constant-time code match).
- ignore → no-op.
The enabled-flag gate applies: feature off ⇒ the row is dropped with no parse/resolve/send.
Sourcepub async fn send_test(&self) -> Result<(), String>
pub async fn send_test(&self) -> Result<(), String>
Send the fixed, clearly-labeled self-test message to the paired handle
and return the outcome (U4). This is a PURE send probe: it composes
TEST_SEND_BODY, sends through the SAME MessageSender the real
approval path uses, and records the outcome into liveness (so a passing
test genuinely proves Automation works and a failing one surfaces the
same way a real send failure does). It mints no per-approval/pairing
code mapping and resolves nothing — the CodeMap is untouched.
Returns Ok(()) when the message was delivered (sent:true), or
Err(reason) for: feature disabled, no paired handle, a hard send error
(Automation denied), or a soft sent:false (recipient-not-found). The
error strings are user-actionable (surfaced verbatim in the pane).
Sourcepub async fn poll_once<FMax, FNew>(
&self,
read_max: FMax,
read_new: FNew,
) -> Result<usize, String>
pub async fn poll_once<FMax, FNew>( &self, read_max: FMax, read_new: FNew, ) -> Result<usize, String>
One poll tick: read new chat.db rows past the persisted watermark,
advance the watermark, and forward each new row to handle_inbound.
Does NO parse/resolve itself — that is handle_inbound’s job; the
poller is purely the change-detection tick.
Watermark semantics: on a fresh/missing watermark, seed it to the
current MAX(ROWID) so no pre-existing text is ever replayed as new
(and return without forwarding anything that first tick). Otherwise read
strictly past last_rowid, forward, then persist the new high-water =
the max rowid seen this tick. The watermark advances monotonically.
read_max/read_new are injected so the test can drive a temp chat.db
(or a synthetic source) with no macOS dependency; production passes the
macOS-gated default-DB readers.
Auto Trait Implementations§
impl !Freeze for MessagingOrchestrator
impl !RefUnwindSafe for MessagingOrchestrator
impl !UnwindSafe for MessagingOrchestrator
impl Send for MessagingOrchestrator
impl Sync for MessagingOrchestrator
impl Unpin for MessagingOrchestrator
impl UnsafeUnpin for MessagingOrchestrator
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
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
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 more