pub struct AfterRunAuditMiddleware { /* private fields */ }Expand description
Blueprint-declared after-run audit hook layer (GH #34).
Wraps every spawn. After a matched step’s inner signal SETTLES (Ok),
dispatches the Blueprint-declared audit agent(s) for that step as an
independent, synthetic sub-task — via Engine::start_task +
Engine::dispatch_attempt_with, the same “recursive swarming” path a
Role::Worker token is allow-listed for (types::WORKER_SWARM_VERBS) —
reusing the AUDITED step’s own worker token. Findings are persisted as
an OutputEvent::Artifact named "audit:<step_ref>" on the AUDITED
step’s own output tail. Downstream steps read those findings via
WorkerPayload.context.steps["audit:<step_ref>"] (fold-final drops
them from the BP-chain value, but Engine::submit_output dual-writes
every Artifact into OutputStore keyed by its own name — see
src/core/engine.rs).
§Invariant (observational-only, binding — issue.md #1/#2/#3)
Every failure in the audit path (spawn/dispatch failure, audit worker
failure, submit failure) is tracing::warn!-logged and swallowed. The
audited step’s own signal, returned to the caller, is ALWAYS the
original inner signal, bit-for-bit — same signal?; ...; Ok(()) shape
as SeniorEscalationMiddleware above, so an inner Err short-circuits
the audit entirely and propagates untouched, and an inner Ok(())
always returns as Ok(()) regardless of what happens inside the audit.
§Recursion guard
An agent name declared as an AuditDef.agent (an “auditor”) is never
itself audited — even if a real flow Step happens to be named after a
declared auditor (e.g. a Blueprint audits every step via steps: None
and also has a flow Step literally named after the auditor). The
audit’s OWN dispatch additionally never revisits this layer to begin
with: it goes through router (the raw CompiledAgentTable —
Compiler::compile’s name→adapter table), not the fully-layered stack
this middleware itself sits inside, so there is no path back into
AfterRunAuditWrapped::spawn from an audit dispatch. The name-set
check in audit_def_matches_step (below) is a second, independent
belt-and-suspenders guard for the real-flow-Step scenario.
Wired conditionally by service::task_launch::TaskLaunchService::launch
(empty Blueprint.audits → no layer, invariant #4 — byte-identical
behavior).
Implementations§
Source§impl AfterRunAuditMiddleware
impl AfterRunAuditMiddleware
Sourcepub fn new(defs: Vec<AuditDef>, router: Arc<CompiledAgentTable>) -> Self
pub fn new(defs: Vec<AuditDef>, router: Arc<CompiledAgentTable>) -> Self
Holds the audit defs relevant to wiring, and the compiled
name→adapter table (Compiler::compile’s CompiledBlueprint.router)
used to dispatch each audit agent by name via
Engine::start_task + Engine::dispatch_attempt_with — the
narrowest handle that resolves an agent name to its
SpawnerAdapter without re-entering this same layer (see the
module comment’s Recursion guard section).
Trait Implementations§
Source§impl SpawnerLayer for AfterRunAuditMiddleware
impl SpawnerLayer for AfterRunAuditMiddleware
Source§fn wrap(&self, inner: Arc<dyn SpawnerAdapter>) -> Arc<dyn SpawnerAdapter> ⓘ
fn wrap(&self, inner: Arc<dyn SpawnerAdapter>) -> Arc<dyn SpawnerAdapter> ⓘ
inner in this layer’s behaviour, returning a new
SpawnerAdapter that delegates to inner (directly or via
wrap_join) while adding this layer’s cross-cutting effect.Auto Trait Implementations§
impl !RefUnwindSafe for AfterRunAuditMiddleware
impl !UnwindSafe for AfterRunAuditMiddleware
impl Freeze for AfterRunAuditMiddleware
impl Send for AfterRunAuditMiddleware
impl Sync for AfterRunAuditMiddleware
impl Unpin for AfterRunAuditMiddleware
impl UnsafeUnpin for AfterRunAuditMiddleware
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> 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