From 6edaec206729f82ab6f2c2b12358cb9552d9e83a Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 16 Aug 2026 19:16:27 +0700 Subject: [PATCH 1/2] style: format ps_usage, and 0.8.9 The file was written with in-place regex rewrites, which produced code rustfmt rejects. CI runs cargo fmt --all --check, so that turned the build red and stopped the release. --- Cargo.lock | 12 +- Cargo.toml | 2 +- crates/agency-tools/src/ps_usage.rs | 198 +++++++++++++++++++++++++--- 3 files changed, 185 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfad63ef..150cc35b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ dependencies = [ [[package]] name = "agency-tools" -version = "0.8.8" +version = "0.8.9" dependencies = [ "derive_more 2.1.1", "dirs", @@ -364,11 +364,11 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "az-core" -version = "0.8.8" +version = "0.8.9" [[package]] name = "az-gui" -version = "0.8.8" +version = "0.8.9" dependencies = [ "agency-proxy-client", "agency-proxy-protocol", @@ -407,7 +407,7 @@ dependencies = [ [[package]] name = "az-mcp-proxy" -version = "0.8.8" +version = "0.8.9" dependencies = [ "az-core", ] @@ -498,7 +498,7 @@ dependencies = [ [[package]] name = "blitz-bench" -version = "0.8.8" +version = "0.8.9" dependencies = [ "blitz-control-protocol", "endpoint-libs", @@ -8978,7 +8978,7 @@ dependencies = [ [[package]] name = "wt-migrate" -version = "0.8.8" +version = "0.8.9" dependencies = [ "chrono", "derive_more 2.1.1", diff --git a/Cargo.toml b/Cargo.toml index 1e958aec..443d5097 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ ] [workspace.package] -version = "0.8.8" +version = "0.8.9" edition = "2024" publish = false diff --git a/crates/agency-tools/src/ps_usage.rs b/crates/agency-tools/src/ps_usage.rs index 585ad006..9a7ebbbd 100644 --- a/crates/agency-tools/src/ps_usage.rs +++ b/crates/agency-tools/src/ps_usage.rs @@ -323,7 +323,11 @@ pub fn build(rows: &[StudyEventRow], start: &str, end: &str) -> eyre::Result = verbs .into_iter() @@ -463,10 +467,7 @@ pub fn render(report: &Report) -> String { report.outcomes.failed )); for entry in &report.outcomes.failed_by_code { - out.push_str(&format!( - " {:<24} {:>6}\n", - entry.code, entry.events - )); + out.push_str(&format!(" {:<24} {:>6}\n", entry.code, entry.events)); } out.push_str(&format!( " without an outcome {:>6}{}\n\n", @@ -549,25 +550,179 @@ mod tests { fn fixture() -> Vec { vec![ // Turn 1: person wrote a directive; agent emitted two. - row("2026-08-10T09:00:00Z", "study-a", "t1", "", PATHWAY_TURN, OPERATION_TURN, "submitted", OUTCOME_OBSERVED, "", r#"{"userAuthoredPs":true}"#), - row("2026-08-10T09:00:01Z", "study-a", "t1", "i1", PATHWAY_DIRECTIVE, "items.add", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), - row("2026-08-10T09:00:02Z", "study-a", "t1", "i1", PATHWAY_DIRECTIVE, "items.add", "completed", OUTCOME_APPLIED, "", "{}"), - row("2026-08-10T09:00:03Z", "study-a", "t1", "i2", PATHWAY_DIRECTIVE, "ask", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), - row("2026-08-10T09:00:04Z", "study-a", "t1", "i2", PATHWAY_DIRECTIVE, "ask", "completed", OUTCOME_APPLIED, "", "{}"), + row( + "2026-08-10T09:00:00Z", + "study-a", + "t1", + "", + PATHWAY_TURN, + OPERATION_TURN, + "submitted", + OUTCOME_OBSERVED, + "", + r#"{"userAuthoredPs":true}"#, + ), + row( + "2026-08-10T09:00:01Z", + "study-a", + "t1", + "i1", + PATHWAY_DIRECTIVE, + "items.add", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), + row( + "2026-08-10T09:00:02Z", + "study-a", + "t1", + "i1", + PATHWAY_DIRECTIVE, + "items.add", + "completed", + OUTCOME_APPLIED, + "", + "{}", + ), + row( + "2026-08-10T09:00:03Z", + "study-a", + "t1", + "i2", + PATHWAY_DIRECTIVE, + "ask", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), + row( + "2026-08-10T09:00:04Z", + "study-a", + "t1", + "i2", + PATHWAY_DIRECTIVE, + "ask", + "completed", + OUTCOME_APPLIED, + "", + "{}", + ), // Turn 2: no directive from the person, one from the agent that failed. - row("2026-08-10T10:00:00Z", "study-a", "t2", "", PATHWAY_TURN, OPERATION_TURN, "submitted", OUTCOME_OBSERVED, "", r#"{"userAuthoredPs":false}"#), - row("2026-08-10T10:00:01Z", "study-a", "t2", "i3", PATHWAY_DIRECTIVE, "pr.link", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), - row("2026-08-10T10:00:02Z", "study-a", "t2", "i3", PATHWAY_DIRECTIVE, "pr.link", "completed", OUTCOME_REFUSED, "unknown_item", "{}"), + row( + "2026-08-10T10:00:00Z", + "study-a", + "t2", + "", + PATHWAY_TURN, + OPERATION_TURN, + "submitted", + OUTCOME_OBSERVED, + "", + r#"{"userAuthoredPs":false}"#, + ), + row( + "2026-08-10T10:00:01Z", + "study-a", + "t2", + "i3", + PATHWAY_DIRECTIVE, + "pr.link", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), + row( + "2026-08-10T10:00:02Z", + "study-a", + "t2", + "i3", + PATHWAY_DIRECTIVE, + "pr.link", + "completed", + OUTCOME_REFUSED, + "unknown_item", + "{}", + ), // Turn 3, next day, different session: no directive either way. - row("2026-08-11T08:00:00Z", "study-b", "t3", "", PATHWAY_TURN, OPERATION_TURN, "submitted", OUTCOME_OBSERVED, "", r#"{"userAuthoredPs":false}"#), + row( + "2026-08-11T08:00:00Z", + "study-b", + "t3", + "", + PATHWAY_TURN, + OPERATION_TURN, + "submitted", + OUTCOME_OBSERVED, + "", + r#"{"userAuthoredPs":false}"#, + ), // Turn 4: a parse with no terminal record, which must surface. - row("2026-08-11T09:00:00Z", "study-b", "t4", "", PATHWAY_TURN, OPERATION_TURN, "submitted", OUTCOME_OBSERVED, "", r#"{"userAuthoredPs":false}"#), - row("2026-08-11T09:00:01Z", "study-b", "t4", "i4", PATHWAY_DIRECTIVE, "items.state", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), + row( + "2026-08-11T09:00:00Z", + "study-b", + "t4", + "", + PATHWAY_TURN, + OPERATION_TURN, + "submitted", + OUTCOME_OBSERVED, + "", + r#"{"userAuthoredPs":false}"#, + ), + row( + "2026-08-11T09:00:01Z", + "study-b", + "t4", + "i4", + PATHWAY_DIRECTIVE, + "items.state", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), // Exactly at the end of the window: included. - row("2026-08-11T23:59:59Z", "study-b", "t5", "i5", PATHWAY_DIRECTIVE, "items.state", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), - row("2026-08-11T23:59:59Z", "study-b", "t5", "i5", PATHWAY_DIRECTIVE, "items.state", "completed", OUTCOME_APPLIED, "", "{}"), + row( + "2026-08-11T23:59:59Z", + "study-b", + "t5", + "i5", + PATHWAY_DIRECTIVE, + "items.state", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), + row( + "2026-08-11T23:59:59Z", + "study-b", + "t5", + "i5", + PATHWAY_DIRECTIVE, + "items.state", + "completed", + OUTCOME_APPLIED, + "", + "{}", + ), // One second past the end: excluded. - row("2026-08-12T00:00:00Z", "study-c", "t6", "i6", PATHWAY_DIRECTIVE, "items.add", STAGE_PARSED, OUTCOME_OBSERVED, "", "{}"), + row( + "2026-08-12T00:00:00Z", + "study-c", + "t6", + "i6", + PATHWAY_DIRECTIVE, + "items.add", + STAGE_PARSED, + OUTCOME_OBSERVED, + "", + "{}", + ), ] } @@ -644,7 +799,10 @@ mod tests { fn outcomes_split_by_bucket_and_missing_terminals_are_reported() { let report = build(&fixture(), START, END).expect("fixture builds"); assert_eq!(report.outcomes.honored, 3); - assert_eq!(report.outcomes.normalized, 0, "the app has no normalized state"); + assert_eq!( + report.outcomes.normalized, 0, + "the app has no normalized state" + ); assert_eq!(report.outcomes.failed, 1); assert_eq!(report.outcomes.failed_by_code.len(), 1); assert_eq!(report.outcomes.failed_by_code[0].code, "unknown_item"); From 577af64ac25628ea5da146f85e78afae631a2003 Mon Sep 17 00:00:00 2001 From: meh Date: Sun, 16 Aug 2026 19:19:27 +0700 Subject: [PATCH 2/2] fix(agency-tools): count one event per directive, not one per stage A directive writes three rows: parsed, queued, then completed. The outcome tally counted every row that was not parsed, so queued and completed each scored and the outcome total came back at exactly twice the event total. The per-day tally counted every directive row whatever its stage, so it came back at exactly three times. One event is now one parsed row, named in EVENT_STAGE and counted in a single block, so the surface, verb and per-day tables cannot drift apart again. Outcomes come only from the terminal stage. The new test asserts all four totals agree, and the fixture carries the queued row that made the arithmetic wrong, so the old behaviour fails it. --- crates/agency-tools/src/ps_usage.rs | 82 +++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/crates/agency-tools/src/ps_usage.rs b/crates/agency-tools/src/ps_usage.rs index 9a7ebbbd..cb922dbc 100644 --- a/crates/agency-tools/src/ps_usage.rs +++ b/crates/agency-tools/src/ps_usage.rs @@ -62,6 +62,23 @@ const OUTCOME_APPLIED: &str = "applied"; const OUTCOME_REFUSED: &str = "refused"; const OUTCOME_OBSERVED: &str = "observed"; +/// The stage carrying a directive's terminal result. +/// +/// One directive writes several rows: `parsed` when it was recognised, then +/// intermediate stages such as `queued`, then this one. Counting outcomes on +/// "any row that is not `parsed`" tallies the same directive more than once, +/// which is how the outcome total came to be an exact multiple of the event +/// total. +const STAGE_COMPLETED: &str = "completed"; + +/// The unit every count in this report is expressed in. +/// +/// One parsed directive is one event. The surface table, the verb table, the +/// per-day table and the outcome table all count that same unit, so their +/// totals are comparable and their sums agree. A row at any other stage is +/// bookkeeping about an event already counted, never a new one. +const EVENT_STAGE: &str = STAGE_PARSED; + /// Which declared surface a verb belongs to. /// /// Derived from the verb rather than stored, because the verb is what the app @@ -274,9 +291,11 @@ pub fn build(rows: &[StudyEventRow], start: &str, end: &str) -> eyre::Result eyre::Result