Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
29a6f0c
fix(global-db): name drifted workflow schema objects in refusals
ScriptedAlchemy Nov 5, 2026
e675fd0
fix(runtime): preserve bootstrap refusals and storage deadlines
ScriptedAlchemy Nov 5, 2026
8dd26c6
fix(automation): stop retrying blocked scheduler ticks
ScriptedAlchemy Nov 5, 2026
564d244
fix(analytics): stop reporting absent authorities as zero
ScriptedAlchemy Nov 5, 2026
284e84f
fix(hints): match repository discovery on whole words
ScriptedAlchemy Nov 5, 2026
fd310d6
fix(global-db): upgrade published workflow artifact table in place
ScriptedAlchemy Nov 5, 2026
bd04c26
test(diagnostics): stop timing the analyzer's first spawn
ScriptedAlchemy Nov 5, 2026
8a0dbd0
fix(global-db): stop census rewriting published artifact rows
ScriptedAlchemy Nov 5, 2026
02eba51
fix(analytics): read automation truncation from the ledger page
ScriptedAlchemy Nov 5, 2026
ba6b712
feat(analytics): serve the recorded failure reason on the recent tape
ScriptedAlchemy Nov 5, 2026
bcb2932
test(automation): prove an empty registered store reports no evidence
ScriptedAlchemy Nov 5, 2026
1dba4d9
fix(global-db): upgrade published workflow table in background
ScriptedAlchemy Nov 5, 2026
da9fdf8
fix(automation): report a reset-blocked scheduler instead of configured
ScriptedAlchemy Nov 5, 2026
9236c31
test(automation): render the reset-blocked scheduler bay end to end
ScriptedAlchemy Nov 5, 2026
5ea4ee2
fix(global-db): retry a failed workflow upgrade in its own task
ScriptedAlchemy Nov 5, 2026
a2ba26e
fix(dashboard): nest scheduler census store patterns
ScriptedAlchemy Nov 5, 2026
14d7608
fix(work): admit output authority before provider effects
ScriptedAlchemy Nov 5, 2026
2117618
fix(build): clear root clippy failures on this branch
ScriptedAlchemy Nov 5, 2026
239ae2f
fix(global-db): report a retrying workflow upgrade as retrying
ScriptedAlchemy Nov 5, 2026
efcab24
test(work): use explicit empty artifact assertions
ScriptedAlchemy Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/tracedecay-agent-hosts/src/hooks/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ mod readiness;

pub use readiness::{
HookCompletedReadinessDistributions, aggregate_hook_completed_readiness,
install_dashboard_hook_readiness_projection,
install_hook_readiness_projection,
};
#[cfg(test)]
use readiness::{
Expand Down
12 changes: 10 additions & 2 deletions crates/tracedecay-agent-hosts/src/hooks/analytics/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,14 @@ fn host_admission_status_rank(status: HostAdmissionStatus) -> u8 {
}
}

/// Readiness rows this fold skips: everything whose `event` is not
/// `hook_completed`. Unavailable fallbacks report this instead of the raw row
/// count, which would claim completed rows were skipped.
fn skipped_non_completed_rows(rows: &[Value]) -> u64 {
u64::try_from(tracedecay_application::analytics_bridge::non_completed_row_count(rows))
.unwrap_or(u64::MAX)
}

struct HookReadinessProjection;

impl tracedecay_application::analytics_bridge::HookReadinessProjectionPort
Expand All @@ -523,7 +531,7 @@ impl tracedecay_application::analytics_bridge::HookReadinessProjectionPort
"input_rows_processed": 0,
"input_rows_dropped_at_cap": 0,
"events_considered": 0,
"events_skipped_non_completed": rows.len(),
"events_skipped_non_completed": skipped_non_completed_rows(rows),
"unavailable_metrics": [{
"metric": "hook_readiness",
"status": "unavailable",
Expand All @@ -537,7 +545,7 @@ impl tracedecay_application::analytics_bridge::HookReadinessProjectionPort
}

#[tracing::instrument(name = "hints.hook_install", level = "trace", skip_all)]
pub fn install_dashboard_hook_readiness_projection() -> tracedecay_domain::errors::Result<()> {
pub fn install_hook_readiness_projection() -> tracedecay_domain::errors::Result<()> {
static INSTALLATION: std::sync::LazyLock<std::result::Result<(), String>> =
std::sync::LazyLock::new(|| {
tracedecay_application::analytics_bridge::install_hook_readiness_projection(
Expand Down
45 changes: 45 additions & 0 deletions crates/tracedecay-agent-hosts/src/hooks/analytics/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,51 @@ fn readiness_aggregate_bounds_untrusted_hook_dimensions() {
}
}

/// The readiness fold is only reachable through the application port, and the
/// port is only answerable once a composition mounts it. Mounting is
/// process-global, so this test proves the mounted composition answers real
/// `hook_completed` rows — and that a second mount is satisfied rather than a
/// conflict.
#[test]
fn installing_the_projection_answers_readiness_through_the_application_port() {
super::install_hook_readiness_projection().unwrap();
// A second composition in the same process must not fail or replace it.
super::install_hook_readiness_projection().unwrap();

let rows = vec![sample_completed(CompletedSample {
agent: "claude",
hook_name: "PostToolUse",
wall_us: Some(12_000),
rtt_us: Some(900),
payload_bytes: Some(1_024),
daemon_ipc_bytes: Some(2_048),
timed_out: Some(false),
budget_ms: Some(50),
disposition: Some(serde_json::json!({
"status": "supported",
"retryable": false,
"class": "application"
})),
})];
let readiness =
tracedecay_application::analytics_bridge::aggregate_hook_completed_readiness(&rows);

assert_eq!(
readiness["collection_status"], "measured",
"the mounted projection must fold real completed rows, not report itself unmounted"
);
assert_eq!(readiness["events_considered"], 1);
assert_eq!(readiness["events_skipped_non_completed"], 0);
assert!(
readiness["unavailable_metrics"]
.as_array()
.is_some_and(|metrics| metrics
.iter()
.all(|metric| metric["metric"] != "hook_readiness")),
"the unmounted-projection blocker must be gone once mounted: {readiness}"
);
}

/// Two owners in one process, each with its own profile, record hook analytics
/// for the same checkout at the same time. Each owner's rows land only in its
/// own profile's store for that checkout.
Expand Down
2 changes: 1 addition & 1 deletion crates/tracedecay-agent-hosts/src/hooks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use analytics::HOOK_ANALYTICS_FILENAME;
use analytics::mint_hint_id;
pub use analytics::{
HookCompletedReadinessDistributions, host_hook_telemetry_contract,
install_dashboard_hook_readiness_projection, measure_host_event_payload_bytes,
install_hook_readiness_projection, measure_host_event_payload_bytes,
};
use analytics::{
HookStdinRefusal, record_hint_analytics, record_hint_emitted, record_hook_invoked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ async fn registered_diagnostics_message_count(
project_sessions: Option<&RegisteredGlobalDb>,
user_sessions: Option<&RegisteredGlobalDb>,
all_projects: bool,
) -> tracedecay_domain::errors::Result<i64> {
let mut total = match project_sessions {
Some(database) => database.session_message_count().await.map_err(cli_error)?,
None => 0,
) -> tracedecay_domain::errors::Result<Option<i64>> {
let mut total: Option<i64> = match project_sessions {
Some(database) => Some(database.session_message_count().await.map_err(cli_error)?),
None => None,
};
if all_projects
&& let Some(database) = user_sessions
&& project_sessions.is_none_or(|project| project.db_path() != database.db_path())
{
total += database.session_message_count().await.map_err(cli_error)?;
let user_total = database.session_message_count().await.map_err(cli_error)?;
total = Some(total.map_or(user_total, |project_total| project_total + user_total));
}
Ok(total)
}
Expand Down
4 changes: 2 additions & 2 deletions crates/tracedecay-application/src/analytics_bridge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub use summary::{
HookAnalyticsRows, HookAnalyticsWindow, HookReadinessProjectionPort,
aggregate_hook_completed_readiness, diagnostics_payload_from_parts,
diagnostics_summary_from_parts, durable_analytics_event_row, hint_efficacy_from_events,
install_hook_readiness_projection, read_hook_analytics_file, read_hook_analytics_rows_at,
recent_hook_rows, sort_hook_analytics_rows,
install_hook_readiness_projection, non_completed_row_count, read_hook_analytics_file,
read_hook_analytics_rows_at, recent_hook_rows, sort_hook_analytics_rows,
};

use std::fs::File;
Expand Down
Loading
Loading