diff --git a/docs/tally/compatibility/compatibility-matrix.json b/docs/tally/compatibility/compatibility-matrix.json
index 61115c08..0728fc12 100644
--- a/docs/tally/compatibility/compatibility-matrix.json
+++ b/docs/tally/compatibility/compatibility-matrix.json
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"bridge_commit_sha": "be1c20cc3fd66fa1ece196505c69f26e555e4b8e",
- "compatibility_surface_sha256": "368223b42559082724513067a7133a94fa4983fdb110ecb18fb57c45b44d51b3",
+ "compatibility_surface_sha256": "d1f7d3d265c7bda1f97b03db15220fd8de9d195cfd7cf34c0466e879cd580db6",
"claims": [
{
"claim_id": "erp9-6-6-3-windows-education-xml-one-company",
diff --git a/docs/tally/compatibility/compatibility-surface.json b/docs/tally/compatibility/compatibility-surface.json
index bcb5a265..6a3f44ec 100644
--- a/docs/tally/compatibility/compatibility-surface.json
+++ b/docs/tally/compatibility/compatibility-surface.json
@@ -731,7 +731,7 @@
},
{
"path": "src-tauri/src/tally/connection.rs",
- "sha256": "76abe63f9b2e8b1fdea9b8b98d818d5e03195f0c50fcfb67d0d6b8fc01e7059b"
+ "sha256": "b09660a80abafdf50f9e64323dec62a4004109f2bc8b8ea959b8544ce8832ab5"
},
{
"path": "src-tauri/src/tally/connector.rs",
@@ -747,7 +747,7 @@
},
{
"path": "src-tauri/src/tally/runtime.rs",
- "sha256": "9f1e388637760ddb0aa5c4de884c9f9dbc66e5714ace4cdd6bdbd2949888a81b"
+ "sha256": "dd8794f1bdb1fbb2b66b21c53e53fe2823ff2a29b00b59ccf1ef2748c58f0101"
},
{
"path": "src-tauri/src/tally/runtime_control.rs",
@@ -958,5 +958,5 @@
"sha256": "a8ac2714fecf51947f2822c8c46d7ce2e8602c732780ff60566a7771f0836f9a"
}
],
- "manifest_sha256": "368223b42559082724513067a7133a94fa4983fdb110ecb18fb57c45b44d51b3"
+ "manifest_sha256": "d1f7d3d265c7bda1f97b03db15220fd8de9d195cfd7cf34c0466e879cd580db6"
}
\ No newline at end of file
diff --git a/scripts/outstandings-evidence.test.mjs b/scripts/outstandings-evidence.test.mjs
index 8a45dbec..d46c5675 100644
--- a/scripts/outstandings-evidence.test.mjs
+++ b/scripts/outstandings-evidence.test.mjs
@@ -9,10 +9,12 @@ import { reportEvidenceDrawerEntry } from "../src/evidence-drawer-entry.ts";
import { readProvenance } from "../src/outstandings-provenance.ts";
test("native provenance labels its receivable-only count while native rows include both directions", async () => {
- const [screen, panel, runtime] = await Promise.all([
+ const [screen, panel, runtime, runtimeTests] = await Promise.all([
readFile(new URL("../src/OutstandingsScreen.tsx", import.meta.url), "utf8"),
readFile(new URL("../src/OutstandingsEvidencePanel.tsx", import.meta.url), "utf8"),
readFile(new URL("../src-tauri/src/tally/runtime.rs", import.meta.url), "utf8"),
+ // The runtime's unit tests live beside it, not inline.
+ readFile(new URL("../src-tauri/src/tally/runtime_tests.rs", import.meta.url), "utf8"),
]);
assert.equal(readProvenance({ read_strategy: "native_bills", source_voucher_count: 0, open_receivable_bill_count: 1 }), "1 open receivable bill read from Tally");
@@ -28,7 +30,7 @@ test("native provenance labels its receivable-only count while native rows inclu
/all_open_bill_rows\(\s*&receivable_rows,\s*&payable_rows,\s*ageing_anchor,\s*&as_of,?\s*\)/.test(runtime),
"native statement rows must consume both receivable and payable sources",
);
- assert.match(runtime, /assert_eq!\(statement_open_bills\.len\(\), 6\);/);
+ assert.match(runtimeTests, /assert_eq!\(statement_open_bills\.len\(\), 6\);/);
assert.match(panel, /readProvenance\(evidence\.readProvenance\)/);
assert.doesNotMatch(panel, /sourceVoucherCount/);
});
diff --git a/src-tauri/src/tally/connection.rs b/src-tauri/src/tally/connection.rs
index 464aef99..62fe351d 100644
--- a/src-tauri/src/tally/connection.rs
+++ b/src-tauri/src/tally/connection.rs
@@ -2020,1853 +2020,5 @@ fn detect_product(text: &str) -> TallyProduct {
}
#[cfg(test)]
-mod tests {
- #[tokio::test]
- async fn capability_probe_preserves_captured_release_and_exclusive_license_tier() {
- let capture = include_bytes!("../../crates/bridge-tally-protocol/tests/fixtures/agent/native-licensed-release-companies.utf16le.xml");
- let xml = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
- capture,
- "text/xml; charset=utf-16",
- bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
- capture.len(),
- )
- .unwrap()
- .text;
- let observation = super::parse_company_gateway_capability_observation(&xml).unwrap();
- for (education, silver, gold, tier) in [
- (false, true, false, Some(super::LicenseTier::Silver)),
- (false, false, true, Some(super::LicenseTier::Gold)),
- (false, true, true, None),
- (false, false, false, None),
- (true, true, false, None),
- ] {
- let mut altered = observation.clone();
- altered.educational_mode = education;
- altered.silver = silver;
- altered.gold = gold;
- assert_eq!(
- super::GatewayProductModeEvidence::from_observation(altered).license_tier,
- tier
- );
- }
- let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
- let address = listener.local_addr().unwrap();
- let server = tokio::spawn(async move {
- for response in [
- utf8_status_response("Unknown status banner"),
- utf16_xml_response(&xml),
- ] {
- let (mut socket, _) = listener.accept().await.unwrap();
- let request = read_complete_http_request(&mut socket).await;
- assert!(!request.is_empty());
- socket.write_all(&response).await.unwrap();
- }
- });
- let (probe, wire) = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .unwrap()
- .probe_with_wire_evidence()
- .await
- .unwrap();
- server.await.unwrap();
- assert!(wire.bytes >= capture.len());
- assert_eq!(probe.companies.len(), 16);
- assert_eq!(probe.profile.profile_version, 4);
- assert_eq!(probe.profile.product, "TallyPrime");
- assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
- assert_eq!(probe.profile.release.as_deref(), Some("7.1"));
- assert_eq!(probe.profile.license_tier, Some(super::LicenseTier::Silver));
- assert_eq!(
- probe.profile.transports[&TransportId::JsonEx].state,
- CapabilityState::Unknown
- );
- assert_eq!(
- probe.profile.transports[&TransportId::JsonEx]
- .safe_reason_code
- .as_deref(),
- Some("transport_not_probed")
- );
- let mut old = serde_json::to_value(&probe.profile).unwrap();
- old.as_object_mut().unwrap().remove("license_tier");
- old["profile_version"] = serde_json::json!(3);
- let old: bridge_tally_core::CapabilityProfile = serde_json::from_value(old).unwrap();
- assert_eq!(old.license_tier, None);
- assert_ne!(old.profile_version, probe.profile.profile_version);
- }
-
- #[test]
- fn party_ledger_commitment_hashes_the_three_encoded_builder_requests() {
- let master = NativeLedgerExportPeriod::new(
- DateBoundaryProfile::ModeAgnostic,
- TallyDate::parse("20260401").unwrap(),
- TallyDate::parse("20260731").unwrap(),
- )
- .unwrap();
- let balance = party_ledger_master_balance_period(
- DateBoundaryProfile::ModeAgnostic,
- TallyDate::parse("20260401").unwrap(),
- TallyDate::parse("20260731").unwrap(),
- )
- .unwrap();
- let requests = [
- super::render_party_ledger_master_request("Synthetic ₹ Books", &master),
- super::render_native_ledger_snapshot_request("Synthetic ₹ Books", &balance),
- super::render_native_group_snapshot_request("Synthetic ₹ Books"),
- ];
- let hashes = requests
- .iter()
- .map(|request| {
- let mut bytes = vec![0xff, 0xfe];
- bytes.extend(request.encode_utf16().flat_map(u16::to_le_bytes));
- super::sha256_hex(&bytes)
- })
- .collect::>();
- let expected = super::sha256_hex(hashes.join(":").as_bytes());
- assert_eq!(super::party_ledger_request_commitment(&requests), expected);
- for index in 0..3 {
- let mut changed = requests.clone();
- changed[index].push(' ');
- assert_ne!(super::party_ledger_request_commitment(&changed), expected);
- }
- }
-
- #[cfg(feature = "voucher-scan")]
- use super::LedgerOpeningCoverageRead;
- use super::{
- canonical_loopback_origin, decode_xml_bytes, detect_product,
- has_presentation_equivalent_guid_siblings, normalize_discovered_companies,
- party_ledger_master_balance_period, party_ledger_master_openings_agree, tally_endpoint,
- unique_company_identities, TallyClient, TallyConfig, TallyProduct, VerifiedCompanyIdentity,
- };
- use bridge_tally_core::{
- CapabilityFeatureId, CapabilityPackId, CapabilityState, EvidenceConfidence, TallyDate,
- TransportId,
- };
- use bridge_tally_protocol::native_outstandings::NativeLedgerExportPeriod;
- use bridge_tally_protocol::outstandings_shared::DateBoundaryProfile;
- use std::time::Duration;
- use tally_protocol_simulator::{Fixture, ScenarioPlan, Simulator, WireEncoding};
- use tokio::io::{AsyncReadExt, AsyncWriteExt};
- use tokio::net::TcpListener;
-
- const COMPANY_EXTENT_V2: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
- );
- const CAPTURED_COMPANY_GUID: &str = "bb8ad19e-6aef-4239-a917-87fec0c6215e";
-
- fn identity_from_company_extent(xml: &str) -> VerifiedCompanyIdentity {
- let companies = bridge_tally_protocol::parse_companies_from_collection(xml)
- .expect("captured company extent must parse as a Company collection");
- let company = companies
- .iter()
- .find(|company| company.guid.as_deref() == Some(CAPTURED_COMPANY_GUID))
- .expect("captured target company must remain present");
- VerifiedCompanyIdentity::from_observed_companies(
- company.name.clone(),
- company.guid.clone().expect("captured target company GUID"),
- company
- .company_number
- .clone()
- .expect("captured target company number"),
- company
- .books_from
- .clone()
- .expect("captured target company books-from"),
- &companies,
- )
- .expect("captured target company tuple must remain uniquely admissible")
- }
-
- fn captured_company_identity() -> VerifiedCompanyIdentity {
- identity_from_company_extent(COMPANY_EXTENT_V2)
- }
-
- #[test]
- fn party_master_snapshot_uses_the_next_common_admissible_boundary() {
- let period = party_ledger_master_balance_period(
- DateBoundaryProfile::ModeAgnostic,
- TallyDate::parse("20260401").unwrap(),
- TallyDate::parse("20260415").unwrap(),
- )
- .expect("the derived common boundary is valid");
- assert_eq!(period.to().as_str(), "20260501");
- assert!(period.to() >= &TallyDate::parse("20260415").unwrap());
- }
-
- #[test]
- fn party_master_opening_balance_comparison_accepts_equivalent_decimal_scale() {
- assert!(party_ledger_master_openings_agree(
- "0",
- &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
- )
- .unwrap());
- }
-
- #[test]
- fn party_master_opening_balance_comparison_withholds_a_real_numeric_mismatch() {
- assert!(!party_ledger_master_openings_agree(
- "0.01",
- &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
- )
- .unwrap());
- }
-
- #[test]
- fn party_master_opening_balance_comparison_rejects_an_unparseable_master_value() {
- assert!(party_ledger_master_openings_agree(
- "not-an-amount",
- &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
- )
- .is_err());
- }
-
- fn utf16_xml_response(body: impl AsRef) -> Vec {
- let body = bridge_tally_protocol::encode_tally_xml_request_utf16le(body.as_ref());
- let headers = format!(
- "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-16\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
- body.len()
- );
- [headers.as_bytes(), &body].concat()
- }
-
- fn utf8_status_response(body: impl AsRef) -> Vec {
- let body = body.as_ref().as_bytes();
- let headers = format!(
- "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
- body.len()
- );
- [headers.as_bytes(), body].concat()
- }
-
- async fn read_complete_http_request(
- socket: &mut (impl tokio::io::AsyncRead + Unpin),
- ) -> Vec {
- const HEADER_TERMINATOR: &[u8] = b"\r\n\r\n";
- const MAX_TEST_HEADER_BYTES: usize = 64 * 1024;
-
- let mut request = Vec::new();
- let mut chunk = [0_u8; 4096];
- let header_end = loop {
- if let Some(offset) = request
- .windows(HEADER_TERMINATOR.len())
- .position(|window| window == HEADER_TERMINATOR)
- {
- break offset + HEADER_TERMINATOR.len();
- }
- let bytes_read = socket
- .read(&mut chunk)
- .await
- .expect("read synthetic HTTP request headers");
- assert!(
- bytes_read > 0,
- "synthetic HTTP request ended before its headers were complete"
- );
- request.extend_from_slice(&chunk[..bytes_read]);
- assert!(
- request.len() <= MAX_TEST_HEADER_BYTES,
- "synthetic HTTP request headers exceeded {MAX_TEST_HEADER_BYTES} bytes"
- );
- };
-
- let headers = std::str::from_utf8(&request[..header_end - HEADER_TERMINATOR.len()])
- .expect("synthetic HTTP request headers must be UTF-8");
- let mut lines = headers.split("\r\n");
- let request_line = lines
- .next()
- .expect("synthetic HTTP request must contain a request line");
- let mut request_line_parts = request_line.split_ascii_whitespace();
- let method = request_line_parts
- .next()
- .expect("synthetic HTTP request method is missing");
- request_line_parts
- .next()
- .expect("synthetic HTTP request target is missing");
- let version = request_line_parts
- .next()
- .expect("synthetic HTTP request version is missing");
- assert_eq!(version, "HTTP/1.1", "synthetic request must use HTTP/1.1");
- assert!(
- request_line_parts.next().is_none(),
- "synthetic HTTP request line has unexpected fields"
- );
-
- let mut content_length = None;
- for header in lines {
- let (name, value) = header
- .split_once(':')
- .expect("synthetic HTTP request contains a malformed header");
- assert!(
- !name.eq_ignore_ascii_case("transfer-encoding"),
- "synthetic HTTP request must use Content-Length framing"
- );
- if name.eq_ignore_ascii_case("content-length") {
- assert!(
- content_length.is_none(),
- "synthetic HTTP request contains duplicate Content-Length headers"
- );
- content_length = Some(
- value
- .trim()
- .parse::()
- .expect("synthetic HTTP request has an invalid Content-Length"),
- );
- }
- }
- let content_length = match content_length {
- Some(length) => length,
- None if method == "GET" || method == "HEAD" => 0,
- None => panic!("synthetic HTTP request body is missing Content-Length"),
- };
- let request_end = header_end
- .checked_add(content_length)
- .expect("synthetic HTTP request length overflowed usize");
- assert!(
- request.len() <= request_end,
- "synthetic HTTP request contains bytes beyond Content-Length"
- );
-
- while request.len() < request_end {
- let remaining = request_end - request.len();
- let chunk_length = remaining.min(chunk.len());
- let bytes_read = socket
- .read(&mut chunk[..chunk_length])
- .await
- .expect("read synthetic HTTP request body");
- assert!(
- bytes_read > 0,
- "synthetic HTTP request ended before its Content-Length body was complete"
- );
- request.extend_from_slice(&chunk[..bytes_read]);
- }
-
- request
- }
-
- fn assert_company_collection_request_shape(request: &str) {
- assert!(request.contains("Collection"));
- for method in ["NAME", "GUID", "PRODUCTNAME"] {
- assert!(request.contains(&format!("{method}")));
- }
- for expression in [
- "EduMode : $$LicenseInfo:IsEducationalMode",
- "Silver : $$LicenseInfo:IsSilver",
- "Gold : $$LicenseInfo:IsGold",
- ] {
- assert!(request.contains(expression));
- }
- }
-
- #[test]
- fn detects_tallyprime_status() {
- assert!(matches!(
- detect_product("TallyPrime Server is Running"),
- TallyProduct::TallyPrime
- ));
- }
-
- #[test]
- fn detects_erp9_status() {
- assert!(matches!(
- detect_product("Tally ERP 9 Server is Running"),
- TallyProduct::TallyErp9
- ));
- }
-
- #[test]
- fn product_marker_is_not_accepted_inside_unrelated_content() {
- assert!(matches!(
- detect_product("TallyPrime Server is Running"),
- TallyProduct::Unknown
- ));
- assert!(matches!(
- detect_product("prefix Tally ERP 9 Server is Running suffix"),
- TallyProduct::Unknown
- ));
- }
-
- #[tokio::test]
- async fn selected_read_request_digest_matches_the_dispatched_wire_entity() {
- let plan = ScenarioPlan::new(Fixture::NormalExport).with_encoding(WireEncoding::Utf16Le);
- let simulator = Simulator::spawn(plan).expect("spawn synthetic Tally endpoint");
- let client = TallyClient::new(TallyConfig {
- host: simulator.address().ip().to_string(),
- port: simulator.address().port(),
- })
- .expect("build synthetic Tally client");
-
- let observation = client
- .qualify_selected_ledgers(
- "BRIDGE SYNTHETIC BOOK",
- "00000000-0000-4000-8000-000000000001",
- )
- .await
- .expect("qualify synthetic selected-ledger read");
- let dispatched = simulator.finish().expect("finish synthetic Tally exchange");
-
- assert_eq!(observation.request_sha256, dispatched.request_body_sha256);
- }
-
- #[test]
- fn company_identity_normalization_rejects_invalid_and_ambiguous_guids() {
- let normalized = normalize_discovered_companies(vec![
- crate::tally::TallyCompany {
- name: " Synthetic A ".to_string(),
- guid: Some(" GUID-1 ".to_string()),
- company_number: Some("100005".to_string()),
- books_from: Some("20250401".to_string()),
- },
- crate::tally::TallyCompany {
- name: "Synthetic B".to_string(),
- guid: Some("guid-1".to_string()),
- company_number: Some("100014".to_string()),
- books_from: Some("20260401".to_string()),
- },
- ])
- .expect("normalize company identities");
- assert_eq!(normalized[0].name, "Synthetic A");
- assert_eq!(normalized[0].guid.as_deref(), Some("GUID-1"));
- assert!(unique_company_identities(&normalized));
- assert!(!has_presentation_equivalent_guid_siblings(&normalized));
-
- assert!(
- normalize_discovered_companies(vec![crate::tally::TallyCompany {
- name: "Synthetic\nCompany".to_string(),
- guid: Some("guid-2".to_string()),
- company_number: None,
- books_from: None,
- }])
- .is_err()
- );
- assert!(
- normalize_discovered_companies(vec![crate::tally::TallyCompany {
- name: "Synthetic Company".to_string(),
- guid: Some("guid\n2".to_string()),
- company_number: None,
- books_from: None,
- }])
- .is_err()
- );
- }
-
- #[test]
- fn presentation_equivalent_guid_siblings_are_not_stable_company_identities() {
- let companies = vec![
- crate::tally::TallyCompany {
- name: "Synthetic Company".to_string(),
- guid: Some("guid-3".to_string()),
- company_number: Some("100005".to_string()),
- books_from: Some("20250401".to_string()),
- },
- crate::tally::TallyCompany {
- name: " synthetic company ".to_string(),
- guid: Some("GUID-3".to_string()),
- company_number: Some("100014".to_string()),
- books_from: Some("20260401".to_string()),
- },
- ];
-
- assert!(unique_company_identities(&companies));
- assert!(has_presentation_equivalent_guid_siblings(&companies));
- }
-
- #[test]
- fn identical_name_same_guid_distinct_books_are_not_stable_company_identities() {
- let companies = vec![
- crate::tally::TallyCompany {
- name: "Synthetic Company".to_string(),
- guid: Some("guid-3".to_string()),
- company_number: Some("100005".to_string()),
- books_from: Some("20250401".to_string()),
- },
- crate::tally::TallyCompany {
- name: "Synthetic Company".to_string(),
- guid: Some("GUID-3".to_string()),
- company_number: Some("100014".to_string()),
- books_from: Some("20260401".to_string()),
- },
- ];
-
- assert!(unique_company_identities(&companies));
- assert!(has_presentation_equivalent_guid_siblings(&companies));
- }
-
- #[test]
- fn validates_tally_endpoint_components() {
- assert_eq!(
- tally_endpoint(&TallyConfig::default(), "/status")
- .expect("localhost endpoint")
- .as_str(),
- "http://127.0.0.1:9000/status"
- );
- let config = TallyConfig {
- host: "::1".to_string(),
- port: 9000,
- };
- assert_eq!(
- tally_endpoint(&config, "/status")
- .expect("IPv6 endpoint")
- .as_str(),
- "http://[::1]:9000/status"
- );
- for host in ["localhost", "127.0.0.1"] {
- assert_eq!(
- canonical_loopback_origin(&TallyConfig {
- host: host.to_string(),
- port: 9000,
- })
- .expect("canonical loopback origin"),
- "http://127.0.0.1:9000"
- );
- }
- assert_eq!(
- canonical_loopback_origin(&TallyConfig {
- host: "::1".to_string(),
- port: 9000,
- })
- .expect("canonical IPv6 loopback origin"),
- "http://[::1]:9000"
- );
-
- for host in ["http://localhost", "localhost/path", "user@localhost", ""] {
- let invalid = TallyConfig {
- host: host.to_string(),
- port: 9000,
- };
- assert!(tally_endpoint(&invalid, "/status").is_err());
- }
-
- for host in [
- "192.168.1.10",
- "10.0.0.5",
- "169.254.1.1",
- "224.0.0.1",
- "8.8.8.8",
- "tally.internal",
- ] {
- let remote = TallyConfig {
- host: host.to_string(),
- port: 9000,
- };
- assert!(tally_endpoint(&remote, "/status").is_err());
- }
- }
-
- #[test]
- fn decodes_supported_xml_byte_order_marks_and_rejects_invalid_sequences() {
- let utf8 = [b"\xEF\xBB\xBF".as_slice(), b""].concat();
- assert_eq!(decode_xml_bytes(utf8).expect("UTF-8 BOM"), "");
-
- let document = "नमस्ते";
- let mut utf16le = vec![0xFF, 0xFE];
- utf16le.extend(document.encode_utf16().flat_map(u16::to_le_bytes));
- assert_eq!(decode_xml_bytes(utf16le).expect("UTF-16LE"), document);
-
- let mut utf16be = vec![0xFE, 0xFF];
- utf16be.extend(document.encode_utf16().flat_map(u16::to_be_bytes));
- assert_eq!(decode_xml_bytes(utf16be).expect("UTF-16BE"), document);
-
- assert!(decode_xml_bytes(vec![0xFF, 0xFE, 0x00]).is_err());
- assert!(decode_xml_bytes(vec![0x80]).is_err());
- }
-
- #[tokio::test]
- async fn tally_requests_ignore_configured_proxy() {
- let tally_listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let tally_address = tally_listener.local_addr().expect("Tally address");
- let proxy_listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic proxy");
- let proxy_address = proxy_listener.local_addr().expect("proxy address");
-
- let tally_server = tokio::spawn(async move {
- let accepted = tokio::time::timeout(Duration::from_secs(2), tally_listener.accept())
- .await
- .expect("Tally request timed out")
- .expect("accept Tally request");
- let (mut socket, _) = accepted;
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- String::from_utf8_lossy(&request).starts_with("GET /status HTTP/1.1"),
- "request should go directly to the Tally endpoint"
- );
- let body = "TallyPrime Server is Running";
- socket
- .write_all(&utf8_status_response(body))
- .await
- .expect("write Tally response");
- });
-
- let proxy_server = tokio::spawn(async move {
- match tokio::time::timeout(Duration::from_millis(750), proxy_listener.accept()).await {
- Ok(Ok((mut socket, _))) => {
- let response = "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
- socket
- .write_all(response.as_bytes())
- .await
- .expect("write proxy response");
- true
- }
- Ok(Err(error)) => panic!("accept proxy request: {error}"),
- Err(_) => false,
- }
- });
-
- let client = TallyClient::with_http_builder(
- TallyConfig {
- host: tally_address.ip().to_string(),
- port: tally_address.port(),
- },
- reqwest::Client::builder().proxy(
- reqwest::Proxy::all(format!("http://{proxy_address}"))
- .expect("synthetic proxy URL"),
- ),
- );
-
- let status = client
- .check_connection()
- .await
- .expect("check synthetic Tally connection");
- tally_server.await.expect("synthetic Tally server task");
- let proxy_received_request = proxy_server.await.expect("synthetic proxy task");
-
- assert!(status.reachable, "direct Tally response should be accepted");
- assert!(
- status.compatible,
- "synthetic Tally status should be recognized"
- );
- assert!(
- !proxy_received_request,
- "Tally traffic must never be sent through a configured proxy"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn paired_outstandings_reads_health_check_between_and_after_requests() {
- const OPTIONAL_VOUCHERS: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/unit_a_optional_voucher_live.xml"
- );
- const STATUS: &str = "TallyPrime Server is Running";
- let identity = captured_company_identity();
-
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- for (index, expected_prefix) in [
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- "POST / HTTP/1.1",
- "GET /status HTTP/1.1",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) =
- tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .expect("paired request timed out")
- .expect("accept paired request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected_prefix),
- "request {index} did not follow the required read/health-check sequence"
- );
- let body = match index {
- 0 | 2 | 12 | 14 => COMPANY_EXTENT_V2,
- 4 | 6 | 8 | 10 => OPTIONAL_VOUCHERS,
- _ => STATUS,
- };
- let response = if index % 2 == 0 {
- utf16_xml_response(body)
- } else {
- utf8_status_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write paired response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let extent = client
- .fetch_company_book_extent(&identity)
- .await
- .expect("paired extent reads remain stable");
- let reporting_window = bridge_tally_protocol::outstandings::DateWindow::parse(
- bridge_tally_protocol::outstandings::DateBoundaryProfile::ModeAgnostic,
- "20260401",
- "20260401",
- )
- .expect("synthetic one-day window");
- let segment_window = reporting_window
- .narrow_partitions()
- .expect("one narrow partition")
- .remove(0);
- let observation = client
- .fetch_outstandings_segment_pair(
- extent.company(),
- segment_window.clone(),
- bridge_tally_protocol::outstandings::AlterIdRange::new(0, 101603)
- .expect("non-empty synthetic range"),
- )
- .await
- .expect("paired segment reads remain stable");
- let verification = observation.verification;
- let bridge_tally_protocol::outstandings::SegmentVerification::Complete(segment) =
- verification
- else {
- panic!("captured voucher response did not verify: {verification:?}");
- };
- assert!(
- !segment.vouchers().is_empty(),
- "captured voucher fixture unexpectedly had no vouchers"
- );
- let witness = client
- .fetch_empty_partition_witness_pair(extent.company(), segment_window)
- .await
- .expect("paired empty-date witness reads remain stable");
- let bridge_tally_protocol::outstandings::WitnessPairVerification::Complete(witness) =
- witness
- else {
- panic!("captured witness fixture did not verify: {witness:?}");
- };
- assert!(
- !witness.vouchers().is_empty(),
- "captured witness fixture unexpectedly had no identity rows"
- );
- let closing_extent = client
- .fetch_company_book_extent(&identity)
- .await
- .expect("closing paired extent reads remain stable");
- assert_eq!(closing_extent, extent, "synthetic book did not change");
- assert_eq!(
- client.observed_body_bytes(),
- Some(
- u64::try_from(
- bridge_tally_protocol::encode_tally_xml_request_utf16le(OPTIONAL_VOUCHERS)
- .len(),
- )
- .expect("encoded fixture length fits u64"),
- ),
- "closing extent evidence must not overwrite the larger voucher payload"
- );
- server.await.expect("synthetic Tally server task");
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn paired_ledger_opening_coverage_reports_intra_pair_drift() {
- const STATUS: &str = "TallyPrime Server is Running";
- let identity = captured_company_identity();
-
- let coverage = |name: &str| {
- format!(
- "bb8ad19e-6aef-4239-a917-87fec0c6215e-00000001Yes0"
- )
- };
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let responses = vec![
- COMPANY_EXTENT_V2.to_string(),
- STATUS.to_string(),
- COMPANY_EXTENT_V2.to_string(),
- STATUS.to_string(),
- coverage("Before Rename"),
- STATUS.to_string(),
- coverage("After Rename"),
- STATUS.to_string(),
- ];
- let server = tokio::spawn(async move {
- for (index, body) in responses.into_iter().enumerate() {
- let (mut socket, _) =
- tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .expect("paired request timed out")
- .expect("accept paired request");
- let request = read_complete_http_request(&mut socket).await;
- let expected_prefix = if index % 2 == 0 {
- "POST / HTTP/1.1"
- } else {
- "GET /status HTTP/1.1"
- };
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected_prefix),
- "request {index} did not follow the required read/health-check sequence"
- );
- let response = if index % 2 == 0 {
- utf16_xml_response(body)
- } else {
- utf8_status_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write paired response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let extent = client
- .fetch_company_book_extent(&identity)
- .await
- .expect("paired extent reads remain stable");
- assert!(matches!(
- client
- .fetch_ledger_opening_coverage(extent.company())
- .await
- .expect("coverage responses parse"),
- LedgerOpeningCoverageRead::Drifted
- ));
- server.await.expect("synthetic Tally server task");
- }
-
- /// The outstandings bracket (`fetch_company_book_extent`, feeding both
- /// `fetch_outstandings_native` and `fetch_ledgers`) must fail closed with
- /// a typed error when both paired reads agree but neither carries
- /// `ALTMSTID`. This is the exact case the review flagged: two
- /// witness-less extents compare equal, so the ordinary `first != second`
- /// drift check alone cannot tell a stable book from one where a
- /// GROUP/LEDGER master moved mid-window without a signal to detect it.
- /// Uses only a targeted mutation of the captured V2 response. The
- /// production parser must continue refusing a stable pair without the
- /// master witness instead of accepting a fabricated positive fixture.
- #[tokio::test]
- async fn outstandings_bracket_fails_closed_when_altmstid_is_absent() {
- const STATUS: &str = "TallyPrime Server is Running";
- const CAPTURED_TARGET_WITNESS: &str = concat!(
- "bb8ad19e-6aef-4239-a917-87fec0c6215e\r\n",
- " 100000\r\n",
- " 101605\r\n",
- " 328"
- );
- const CAPTURED_TARGET_WITHOUT_WITNESS: &str = concat!(
- "bb8ad19e-6aef-4239-a917-87fec0c6215e\r\n",
- " 100000\r\n",
- " 101605"
- );
- let identity = captured_company_identity();
- assert_eq!(
- COMPANY_EXTENT_V2.matches(CAPTURED_TARGET_WITNESS).count(),
- 1,
- "the exact captured target witness must appear once before mutation"
- );
- let company_extent_without_altmstid =
- COMPANY_EXTENT_V2.replacen(CAPTURED_TARGET_WITNESS, CAPTURED_TARGET_WITHOUT_WITNESS, 1);
- assert!(
- !company_extent_without_altmstid.contains(CAPTURED_TARGET_WITNESS),
- "the captured target witness mutation must apply"
- );
- let parsed_without_witness =
- bridge_tally_protocol::outstandings_shared::parse_company_book_extent_v2(
- &company_extent_without_altmstid,
- &identity
- .company_book_extent_expectation()
- .expect("captured expectation"),
- )
- .expect("the captured negative mutation must remain a parseable extent");
- assert!(
- parsed_without_witness
- .master_alter_id_high_water()
- .is_none(),
- "the target witness mutation must remove only the target ALTMSTID"
- );
-
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let responses = [
- company_extent_without_altmstid.clone(),
- STATUS.to_string(),
- company_extent_without_altmstid,
- STATUS.to_string(),
- ];
- let server = tokio::spawn(async move {
- for (index, body) in responses.into_iter().enumerate() {
- let (mut socket, _) =
- tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .expect("paired request timed out")
- .expect("accept paired request");
- let request = read_complete_http_request(&mut socket).await;
- let expected_prefix = if index % 2 == 0 {
- "POST / HTTP/1.1"
- } else {
- "GET /status HTTP/1.1"
- };
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected_prefix),
- "request {index} did not follow the required read/health-check sequence"
- );
- let response = if index % 2 == 0 {
- utf16_xml_response(&body)
- } else {
- utf8_status_response(&body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write paired response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let error = client
- .fetch_company_book_extent(&identity)
- .await
- .expect_err("a stable pair without ALTMSTID must still fail closed");
- server.await.expect("synthetic Tally server task");
- assert!(
- error
- .downcast_ref::()
- .is_some_and(|error| {
- *error
- == bridge_tally_protocol::outstandings_shared::OutstandingsError::MasterWitnessAbsent
- }),
- "unexpected error: {error:#}"
- );
- }
-
- #[tokio::test]
- async fn http_success_with_tally_status_zero_is_not_an_empty_success() {
- let identity = captured_company_identity();
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- for (index, body) in [
- COMPANY_EXTENT_V2,
- "TallyPrime Server is Running",
- COMPANY_EXTENT_V2,
- "TallyPrime Server is Running",
- "",
- "TallyPrime Server is Running",
- "",
- "TallyPrime Server is Running",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- let expected = if index == 1 || index == 3 || index == 5 || index == 7 {
- "GET /status HTTP/1.1"
- } else {
- "POST / HTTP/1.1"
- };
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected),
- "ledger fetch did not preserve its extent/read sequence"
- );
- if index == 4 || index == 6 {
- let body_start = request
- .windows(4)
- .position(|window| window == b"\r\n\r\n")
- .map(|offset| offset + 4)
- .expect("native ledger POST has complete HTTP headers");
- let request_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
- &request[body_start..],
- request.len(),
- )
- .expect("native ledger POST uses decodable UTF-16 XML")
- .text;
- assert!(
- request_xml.contains(r#"20240401"#)
- );
- assert!(request_xml.contains(r#"20260401"#));
- }
- let response = if index == 1 || index == 3 || index == 5 || index == 7 {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let error = client
- .fetch_ledgers(&identity, DateBoundaryProfile::ModeAgnostic)
- .await
- .expect_err("STATUS 0 must not become an empty ledger result");
- server.await.expect("synthetic Tally server task");
- assert!(
- error
- .to_string()
- .contains("native ledger collection did not report success"),
- "unexpected error: {error:#}"
- );
- }
-
- #[tokio::test]
- async fn invalid_book_extent_stops_ledger_export_without_a_date_fallback() {
- let identity = captured_company_identity();
- let invalid_extent =
- COMPANY_EXTENT_V2.replacen(r#"20240401"#, "", 1);
- assert_ne!(invalid_extent, COMPANY_EXTENT_V2);
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- for (index, body) in [
- invalid_extent.as_str(),
- "TallyPrime Server is Running",
- invalid_extent.as_str(),
- "TallyPrime Server is Running",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept extent request");
- let request = read_complete_http_request(&mut socket).await;
- let expected = if index % 2 == 0 {
- "POST / HTTP/1.1"
- } else {
- "GET /status HTTP/1.1"
- };
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected),
- "request {index} did not follow the paired extent sequence"
- );
- let response = if index % 2 == 0 {
- utf16_xml_response(body)
- } else {
- utf8_status_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write extent response");
- }
- assert!(
- tokio::time::timeout(Duration::from_millis(200), listener.accept())
- .await
- .is_err(),
- "an invalid extent must stop before any native ledger request"
- );
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- client
- .fetch_ledgers(&identity, DateBoundaryProfile::ModeAgnostic)
- .await
- .expect_err("missing BOOKSFROM must fail closed");
- server.await.expect("synthetic Tally server task");
- }
-
- #[tokio::test]
- async fn education_profile_rejects_an_unsupported_books_from_before_ledger_export() {
- let extent = COMPANY_EXTENT_V2.replacen(
- r#"20240401"#,
- r#"20240115"#,
- 1,
- );
- let identity = identity_from_company_extent(&extent);
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- for (index, body) in [
- extent.as_str(),
- "TallyPrime Server is Running",
- extent.as_str(),
- "TallyPrime Server is Running",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept extent request");
- let request = read_complete_http_request(&mut socket).await;
- let expected = if index % 2 == 0 {
- "POST / HTTP/1.1"
- } else {
- "GET /status HTTP/1.1"
- };
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected),
- "request {index} did not follow the paired extent sequence"
- );
- let response = if index % 2 == 0 {
- utf16_xml_response(body)
- } else {
- utf8_status_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write extent response");
- }
- assert!(
- tokio::time::timeout(Duration::from_millis(200), listener.accept())
- .await
- .is_err(),
- "an Education-invalid BOOKSFROM must stop before the native ledger request"
- );
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let error = client
- .fetch_ledgers(&identity, DateBoundaryProfile::EducationRestricted)
- .await
- .expect_err("unsupported boundary must not reach the ledger export");
- server.await.expect("synthetic Tally server task");
- assert!(
- error
- .to_string()
- .contains("not supported by the endpoint compatibility profile"),
- "unexpected error: {error:#}"
- );
- }
-
- fn native_voucher_collection_xml(rows: &[&str]) -> String {
- format!(
- "{}",
- rows.join("")
- )
- }
-
- const SYNTHETIC_VOUCHER_ROW: &str = r#"20260401synthetic-company-guid-0000000111PaymentNoNoCash-100.00Yes"#;
-
- /// A native Voucher collection carries no envelope company GUID, and a
- /// zero-row response has no per-row GUID either -- there is nothing for
- /// `parse_native_voucher_source_records_with_evidence` to bind. Before
- /// the fix, `fetch_vouchers` accepted such a response unauthenticated,
- /// so a silently substituted or dropped company looked identical to a
- /// genuinely empty window. This reproduces that: the voucher read comes
- /// back empty, and the out-of-band book-extent bracket that should
- /// confirm the pinned company instead observes a different one.
- #[tokio::test]
- async fn empty_voucher_response_is_rejected_when_pinned_company_cannot_be_confirmed() {
- let identity = captured_company_identity();
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let empty_vouchers = native_voucher_collection_xml(&[]);
- let substituted_extent =
- COMPANY_EXTENT_V2.replacen(CAPTURED_COMPANY_GUID, "substituted-company-guid", 1);
- let status = "TallyPrime Server is Running";
- let steps: Vec<(&str, String, bool)> = vec![
- ("POST / HTTP/1.1", empty_vouchers, false),
- ("POST / HTTP/1.1", substituted_extent.clone(), false),
- ("GET /status HTTP/1.1", status.to_string(), true),
- ("POST / HTTP/1.1", substituted_extent, false),
- ("GET /status HTTP/1.1", status.to_string(), true),
- ];
- let server = tokio::spawn(async move {
- for (index, (expected_prefix, body, is_status)) in steps.into_iter().enumerate() {
- let (mut socket, _) =
- tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .unwrap_or_else(|_| {
- panic!(
- "request {index} timed out -- the empty-voucher rejection path \
- did not attempt to confirm the pinned company out of band"
- )
- })
- .expect("accept synthetic Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected_prefix),
- "request {index} did not follow the voucher-then-extent-bracket sequence"
- );
- let response = if is_status {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write synthetic Tally response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let error = client
- .fetch_vouchers(&identity, "20260401", "20260401")
- .await
- .expect_err(
- "an empty voucher response must not be accepted when the pinned company book \
- extent cannot be confirmed",
- );
- server.await.expect("synthetic Tally server task");
- assert!(
- error.to_string().contains(
- "empty voucher response could not confirm the pinned company book extent"
- ),
- "unexpected error: {error:#}"
- );
- }
-
- /// Same empty voucher response as above, but this time the out-of-band
- /// book-extent bracket confirms the pinned company is still selected and
- /// stable -- so the empty result is accepted.
- #[tokio::test]
- async fn empty_voucher_response_is_accepted_when_bracket_confirms_pinned_company() {
- let identity = captured_company_identity();
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let empty_vouchers = native_voucher_collection_xml(&[]);
- let confirmed_extent = COMPANY_EXTENT_V2.to_string();
- let status = "TallyPrime Server is Running";
- let steps: Vec<(&str, String, bool)> = vec![
- ("POST / HTTP/1.1", empty_vouchers, false),
- ("POST / HTTP/1.1", confirmed_extent.clone(), false),
- ("GET /status HTTP/1.1", status.to_string(), true),
- ("POST / HTTP/1.1", confirmed_extent, false),
- ("GET /status HTTP/1.1", status.to_string(), true),
- ];
- let request_count = steps.len();
- let server = tokio::spawn(async move {
- for (index, (expected_prefix, body, is_status)) in steps.into_iter().enumerate() {
- let (mut socket, _) =
- tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .unwrap_or_else(|_| panic!("request {index} timed out"))
- .expect("accept synthetic Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- String::from_utf8_lossy(&request).starts_with(expected_prefix),
- "request {index} did not follow the voucher-then-extent-bracket sequence"
- );
- let response = if is_status {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write synthetic Tally response");
- }
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let vouchers = client
- .fetch_vouchers(&identity, "20260401", "20260401")
- .await
- .expect("an empty voucher response confirmed by the extent bracket must be accepted");
- server.await.expect("synthetic Tally server task");
- assert!(vouchers.is_empty());
- assert_eq!(
- request_count, 5,
- "the empty path must pay for exactly one voucher read plus the extent bracket"
- );
- }
-
- /// A non-empty voucher response keeps its existing row-GUID binding and
- /// must not pay for the extent bracket -- the common case stays a single
- /// request.
- #[tokio::test]
- async fn non_empty_voucher_response_issues_no_extra_request() {
- let identity = captured_company_identity();
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let voucher_row =
- SYNTHETIC_VOUCHER_ROW.replace("synthetic-company-guid", CAPTURED_COMPANY_GUID);
- let non_empty_vouchers = native_voucher_collection_xml(&[&voucher_row]);
-
- let server = tokio::spawn(async move {
- let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
- .await
- .expect("voucher request timed out")
- .expect("accept synthetic Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- String::from_utf8_lossy(&request).starts_with("POST / HTTP/1.1"),
- "unexpected request for the non-empty voucher read"
- );
- socket
- .write_all(&utf16_xml_response(non_empty_vouchers))
- .await
- .expect("write synthetic Tally response");
-
- // A non-empty response must not pay for the extent bracket: no
- // further connection should ever arrive.
- let extra = tokio::time::timeout(Duration::from_millis(300), listener.accept()).await;
- assert!(
- extra.is_err(),
- "non-empty voucher fetch issued an unexpected extra request"
- );
- });
-
- let client = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client");
- let vouchers = client
- .fetch_vouchers(&identity, "20260401", "20260401")
- .await
- .expect("non-empty voucher fetch must still succeed exactly as today");
- server.await.expect("synthetic Tally server task");
- assert_eq!(vouchers.len(), 1);
- }
-
- #[tokio::test]
- async fn capability_probe_reports_only_observed_xml_support() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- for (index, body) in [
- "LOCAL STATUS HEURISTIC UNRECOGNIZED",
- "guid-110000120260401TallyPrimeNoYesNo",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(!request.is_empty(), "synthetic Tally request must not be empty");
- let response = if index == 0 {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- });
-
- let probe = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .probe()
- .await
- .expect("probe synthetic Tally endpoint");
- server.await.expect("synthetic Tally server task");
-
- assert!(probe.connection.reachable);
- assert!(!probe.connection.compatible);
- assert_eq!(probe.companies.len(), 1);
- assert_eq!(
- probe.profile.transports[&TransportId::XmlHttp].state,
- CapabilityState::Supported
- );
- assert_eq!(
- probe.profile.packs[&CapabilityPackId::CoreAccounting].state,
- CapabilityState::Unknown
- );
- assert_eq!(probe.profile.product, "TallyPrime");
- assert!(probe.profile.release.is_none());
- assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
- assert_eq!(probe.profile.profile_version, 4);
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::ProductAndMode].state,
- CapabilityState::Supported
- );
- let boundary = crate::tally::TallyRuntime::default()
- .master_ledger_export_boundary_profile_from_profile(Some(&probe.profile));
- assert_eq!(boundary, DateBoundaryProfile::ModeAgnostic);
- assert!(NativeLedgerExportPeriod::new(
- boundary,
- TallyDate::parse("20240115").expect("valid mid-month date"),
- TallyDate::parse("20240115").expect("valid mid-month date"),
- )
- .is_ok());
- for transport in [TransportId::TdlCompanion, TransportId::Odbc] {
- let evidence = &probe.profile.transports[&transport];
- assert_eq!(evidence.state, CapabilityState::Unknown);
- assert_eq!(evidence.confidence, EvidenceConfidence::Unknown);
- assert_eq!(
- evidence.safe_reason_code.as_deref(),
- Some("configuration_not_observed")
- );
- }
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::EndpointReachability].state,
- CapabilityState::Supported
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::LoadedCompanies].state,
- CapabilityState::Supported
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
- CapabilityState::Supported
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::EncodingBehaviour]
- .safe_reason_code
- .as_deref(),
- Some("utf16_le_bom_observed")
- );
- for feature in [
- CapabilityFeatureId::PracticalResponseLimit,
- CapabilityFeatureId::LedgerRead,
- CapabilityFeatureId::VoucherRead,
- CapabilityFeatureId::Write,
- ] {
- assert_eq!(
- probe.profile.features[&feature].state,
- CapabilityState::Unknown
- );
- }
- }
-
- #[tokio::test]
- async fn capability_probe_records_unavailable_product_mode_evidence_without_refusing() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- let responses = [
- utf8_status_response("TallyPrime Server is Running"),
- utf16_xml_response(
- "Capability collection unavailable",
- ),
- utf16_xml_response(
- "Synthetic Companyguid-1",
- ),
- ];
- for response in responses {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- !request.is_empty(),
- "synthetic Tally request must not be empty"
- );
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- });
-
- let probe = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .probe()
- .await
- .expect("unavailable product/mode evidence must not refuse the probe");
- server.await.expect("synthetic Tally server task");
-
- assert_eq!(probe.profile.product, "Unknown");
- assert!(probe.profile.mode.is_none());
- assert_eq!(
- crate::tally::TallyRuntime::default()
- .master_ledger_export_boundary_profile_from_profile(Some(&probe.profile)),
- DateBoundaryProfile::ModeAgnostic
- );
- let evidence = &probe.profile.features[&CapabilityFeatureId::ProductAndMode];
- assert_eq!(evidence.state, CapabilityState::Unknown);
- assert_eq!(evidence.confidence, EvidenceConfidence::Observed);
- assert_eq!(
- evidence.safe_reason_code.as_deref(),
- Some("product_mode_evidence_unavailable")
- );
- }
-
- /// `fetch_companies` now requests the native `Company` collection
- /// (`ReadOnlyProfile::CompanyListV2`) instead of the legacy `CompanyListV1`
- /// custom TDL report. This asserts the request itself carries that shape --
- /// `TYPE=Collection`, no `REPORT`/`FORM`/`PART`/`LINE`/`FIELD` stack, and no
- /// `SVCURRENTCOMPANY` scoping a discovery read to one company -- and that a
- /// company row's `NAME` attribute and nested `GUID` are trimmed and returned.
- #[tokio::test]
- async fn interactive_company_fetch_sends_the_native_collection_request() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- let body = r#" guid-1 "#;
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- let body_start = request
- .windows(4)
- .position(|window| window == b"\r\n\r\n")
- .map(|position| position + 4)
- .expect("POST request has complete HTTP headers");
- let post_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
- &request[body_start..],
- request.len(),
- )
- .expect("POST request uses decodable UTF-16 XML")
- .text;
- assert_company_collection_request_shape(&post_xml);
- assert!(!post_xml.contains(""#;
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(
- !request.is_empty(),
- "synthetic Tally request must not be empty"
- );
- socket
- .write_all(&utf16_xml_response(body))
- .await
- .expect("write Tally response");
- });
-
- let error = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .fetch_companies()
- .await
- .expect_err("a company row without a GUID must fail closed");
- server.await.expect("synthetic Tally server task");
- assert!(error.to_string().contains("GUID"));
- let retained = error
- .downcast_ref::()
- .expect("parser refusal retains its typed wire-evidence boundary");
- assert!(retained.evidence.bytes > 0);
- }
-
- #[tokio::test]
- async fn direct_company_bootstrap_uses_only_the_shaped_collection_identity() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- // `fetch_companies` (the first of the two reads below) now requests the
- // native `Company` collection (`ReadOnlyProfile::CompanyListV2`), so its
- // response carries that shape rather than the legacy `CompanyListV1`
- // direct report. Its GUID must still not escape into the returned
- // identity -- only the second, scoped `standard` read may do that.
- let discovered = r#"scoped-guid10000120260401"#;
- let standard = "ledger-guidPrimaryscoped-guidSynthetic Company1033";
- let server = tokio::spawn(async move {
- for body in [discovered, standard] {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(!request.is_empty());
- socket
- .write_all(&utf16_xml_response(body))
- .await
- .expect("write Tally response");
- }
- });
-
- let company = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .bootstrap_direct_company("Synthetic Company")
- .await
- .expect("strict scoped bootstrap should succeed");
- server.await.expect("synthetic Tally server task");
-
- assert_eq!(company.name, "Synthetic Company");
- assert_eq!(company.guid.as_deref(), Some("scoped-guid"));
- assert_eq!(company.company_number.as_deref(), Some("100001"));
- assert_eq!(company.books_from.as_deref(), Some("20260401"));
- }
-
- #[tokio::test]
- async fn capability_probe_does_not_promote_a_direct_company_report() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- // This responder gives the same untrusted bare direct company
- // report regardless of what is requested: once for the `V2`
- // collection attempt (which the collection parser rejects, since
- // it never satisfies `HEADER/STATUS`), and once more for the
- // `V1` fallback that follows.
- for (index, body) in [
- "LOCAL STATUS HEURISTIC UNRECOGNIZED",
- "Synthetic Companyguid-1",
- "Synthetic Companyguid-1",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(!request.is_empty(), "synthetic Tally request must not be empty");
- let response = if index == 0 {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- });
-
- let probe = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .probe()
- .await
- .expect("probe synthetic Tally endpoint");
- server.await.expect("synthetic Tally server task");
-
- assert!(probe.connection.reachable);
- assert!(probe.companies.is_empty());
- assert_eq!(
- probe.profile.transports[&TransportId::XmlHttp].state,
- CapabilityState::Unknown
- );
- assert_eq!(
- probe.profile.transports[&TransportId::XmlHttp]
- .safe_reason_code
- .as_deref(),
- Some("direct_company_report_untrusted")
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::CompanyRead].state,
- CapabilityState::Unknown
- );
- }
-
- #[tokio::test]
- async fn capability_probe_does_not_promote_a_shaped_company_failure_to_xml_support() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- // Same shaped `STATUS=0` failure for both the `V2` collection
- // attempt and the `V1` fallback that follows it.
- for (index, body) in [
- "TallyPrime Server is Running",
- "Could not find Company ''",
- "Could not find Company ''",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- assert!(!request.is_empty(), "synthetic Tally request must not be empty");
- let response = if index == 0 {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- });
-
- let probe = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .probe()
- .await
- .expect("probe synthetic Tally endpoint");
- server.await.expect("synthetic Tally server task");
-
- assert!(probe.companies.is_empty());
- let xml = &probe.profile.transports[&TransportId::XmlHttp];
- assert_eq!(xml.state, CapabilityState::Unknown);
- assert_eq!(xml.confidence, EvidenceConfidence::Observed);
- assert_eq!(xml.safe_reason_code.as_deref(), Some("company_not_loaded"));
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::LoadedCompanies].state,
- CapabilityState::Unknown
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
- CapabilityState::Unknown
- );
- }
-
- /// A gateway-shaped `Company` collection response with two loaded
- /// synthetic companies and the `CMPINFO` counter trap included. Proves
- /// `probe` requests `CompanyListV2` on the happy path
- /// and trusts its success without ever falling back to the legacy
- /// `CompanyListV1` report: the mock server has exactly one POST response
- /// queued, so a fallback request would hang and fail this test.
- #[tokio::test]
- async fn capability_probe_marks_presentation_equivalent_guid_siblings_ambiguous() {
- let listener = TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic Tally server");
- let address = listener.local_addr().expect("synthetic Tally address");
- let server = tokio::spawn(async move {
- let mut requests = Vec::new();
- for (index, body) in [
- "TallyPrime Server is Running",
- "\n \n 0\n \n Synthetic Company Asynthetic-guid-a10000120260401TallyPrimeNoYesNo\n synthetic company a SYNTHETIC-GUID-A10000220270401TallyPrimeNoYesNo\n \n \n",
- ]
- .into_iter()
- .enumerate()
- {
- let (mut socket, _) = listener.accept().await.expect("accept Tally request");
- let request = read_complete_http_request(&mut socket).await;
- requests.push(request);
- let response = if index == 0 {
- utf8_status_response(body)
- } else {
- utf16_xml_response(body)
- };
- socket
- .write_all(&response)
- .await
- .expect("write Tally response");
- }
- requests
- });
-
- let probe = TallyClient::new(TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- })
- .expect("build synthetic Tally client")
- .probe()
- .await
- .expect("probe synthetic Tally endpoint");
- let requests = server.await.expect("synthetic Tally server task");
-
- assert_eq!(probe.companies.len(), 2);
- assert_eq!(probe.companies[0].name, "Synthetic Company A");
- assert_eq!(probe.companies[0].guid.as_deref(), Some("synthetic-guid-a"));
- assert_eq!(probe.companies[1].name, "synthetic company a");
- assert_eq!(probe.companies[1].guid.as_deref(), Some("SYNTHETIC-GUID-A"));
- assert_eq!(probe.profile.product, "TallyPrime");
- assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
- assert_eq!(
- probe.profile.transports[&TransportId::XmlHttp].state,
- CapabilityState::Supported
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity]
- .safe_reason_code
- .as_deref(),
- Some("company_identity_display_scope_ambiguous")
- );
- assert_eq!(
- probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
- CapabilityState::Unknown
- );
-
- let post_request = requests
- .iter()
- .find(|request| request.starts_with(b"POST"))
- .expect("exactly one POST request was sent");
- let body_start = post_request
- .windows(4)
- .position(|window| window == b"\r\n\r\n")
- .map(|position| position + 4)
- .expect("POST request has complete HTTP headers");
- let post_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
- &post_request[body_start..],
- post_request.len(),
- )
- .expect("POST request uses decodable UTF-16 XML");
- assert_company_collection_request_shape(&post_xml.text);
- assert!(post_xml.text.contains("BridgeCompanyExtent"));
- }
-}
+#[path = "connection_tests.rs"]
+mod tests;
diff --git a/src-tauri/src/tally/connection_tests.rs b/src-tauri/src/tally/connection_tests.rs
new file mode 100644
index 00000000..97589be0
--- /dev/null
+++ b/src-tauri/src/tally/connection_tests.rs
@@ -0,0 +1,1836 @@
+#[tokio::test]
+async fn capability_probe_preserves_captured_release_and_exclusive_license_tier() {
+ let capture = include_bytes!("../../crates/bridge-tally-protocol/tests/fixtures/agent/native-licensed-release-companies.utf16le.xml");
+ let xml = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
+ capture,
+ "text/xml; charset=utf-16",
+ bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
+ capture.len(),
+ )
+ .unwrap()
+ .text;
+ let observation = super::parse_company_gateway_capability_observation(&xml).unwrap();
+ for (education, silver, gold, tier) in [
+ (false, true, false, Some(super::LicenseTier::Silver)),
+ (false, false, true, Some(super::LicenseTier::Gold)),
+ (false, true, true, None),
+ (false, false, false, None),
+ (true, true, false, None),
+ ] {
+ let mut altered = observation.clone();
+ altered.educational_mode = education;
+ altered.silver = silver;
+ altered.gold = gold;
+ assert_eq!(
+ super::GatewayProductModeEvidence::from_observation(altered).license_tier,
+ tier
+ );
+ }
+ let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
+ let address = listener.local_addr().unwrap();
+ let server = tokio::spawn(async move {
+ for response in [
+ utf8_status_response("Unknown status banner"),
+ utf16_xml_response(&xml),
+ ] {
+ let (mut socket, _) = listener.accept().await.unwrap();
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(!request.is_empty());
+ socket.write_all(&response).await.unwrap();
+ }
+ });
+ let (probe, wire) = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .unwrap()
+ .probe_with_wire_evidence()
+ .await
+ .unwrap();
+ server.await.unwrap();
+ assert!(wire.bytes >= capture.len());
+ assert_eq!(probe.companies.len(), 16);
+ assert_eq!(probe.profile.profile_version, 4);
+ assert_eq!(probe.profile.product, "TallyPrime");
+ assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
+ assert_eq!(probe.profile.release.as_deref(), Some("7.1"));
+ assert_eq!(probe.profile.license_tier, Some(super::LicenseTier::Silver));
+ assert_eq!(
+ probe.profile.transports[&TransportId::JsonEx].state,
+ CapabilityState::Unknown
+ );
+ assert_eq!(
+ probe.profile.transports[&TransportId::JsonEx]
+ .safe_reason_code
+ .as_deref(),
+ Some("transport_not_probed")
+ );
+ let mut old = serde_json::to_value(&probe.profile).unwrap();
+ old.as_object_mut().unwrap().remove("license_tier");
+ old["profile_version"] = serde_json::json!(3);
+ let old: bridge_tally_core::CapabilityProfile = serde_json::from_value(old).unwrap();
+ assert_eq!(old.license_tier, None);
+ assert_ne!(old.profile_version, probe.profile.profile_version);
+}
+
+#[test]
+fn party_ledger_commitment_hashes_the_three_encoded_builder_requests() {
+ let master = NativeLedgerExportPeriod::new(
+ DateBoundaryProfile::ModeAgnostic,
+ TallyDate::parse("20260401").unwrap(),
+ TallyDate::parse("20260731").unwrap(),
+ )
+ .unwrap();
+ let balance = party_ledger_master_balance_period(
+ DateBoundaryProfile::ModeAgnostic,
+ TallyDate::parse("20260401").unwrap(),
+ TallyDate::parse("20260731").unwrap(),
+ )
+ .unwrap();
+ let requests = [
+ super::render_party_ledger_master_request("Synthetic ₹ Books", &master),
+ super::render_native_ledger_snapshot_request("Synthetic ₹ Books", &balance),
+ super::render_native_group_snapshot_request("Synthetic ₹ Books"),
+ ];
+ let hashes = requests
+ .iter()
+ .map(|request| {
+ let mut bytes = vec![0xff, 0xfe];
+ bytes.extend(request.encode_utf16().flat_map(u16::to_le_bytes));
+ super::sha256_hex(&bytes)
+ })
+ .collect::>();
+ let expected = super::sha256_hex(hashes.join(":").as_bytes());
+ assert_eq!(super::party_ledger_request_commitment(&requests), expected);
+ for index in 0..3 {
+ let mut changed = requests.clone();
+ changed[index].push(' ');
+ assert_ne!(super::party_ledger_request_commitment(&changed), expected);
+ }
+}
+
+#[cfg(feature = "voucher-scan")]
+use super::LedgerOpeningCoverageRead;
+use super::{
+ canonical_loopback_origin, decode_xml_bytes, detect_product,
+ has_presentation_equivalent_guid_siblings, normalize_discovered_companies,
+ party_ledger_master_balance_period, party_ledger_master_openings_agree, tally_endpoint,
+ unique_company_identities, TallyClient, TallyConfig, TallyProduct, VerifiedCompanyIdentity,
+};
+use bridge_tally_core::{
+ CapabilityFeatureId, CapabilityPackId, CapabilityState, EvidenceConfidence, TallyDate,
+ TransportId,
+};
+use bridge_tally_protocol::native_outstandings::NativeLedgerExportPeriod;
+use bridge_tally_protocol::outstandings_shared::DateBoundaryProfile;
+use std::time::Duration;
+use tally_protocol_simulator::{Fixture, ScenarioPlan, Simulator, WireEncoding};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpListener;
+
+const COMPANY_EXTENT_V2: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
+);
+const CAPTURED_COMPANY_GUID: &str = "bb8ad19e-6aef-4239-a917-87fec0c6215e";
+
+fn identity_from_company_extent(xml: &str) -> VerifiedCompanyIdentity {
+ let companies = bridge_tally_protocol::parse_companies_from_collection(xml)
+ .expect("captured company extent must parse as a Company collection");
+ let company = companies
+ .iter()
+ .find(|company| company.guid.as_deref() == Some(CAPTURED_COMPANY_GUID))
+ .expect("captured target company must remain present");
+ VerifiedCompanyIdentity::from_observed_companies(
+ company.name.clone(),
+ company.guid.clone().expect("captured target company GUID"),
+ company
+ .company_number
+ .clone()
+ .expect("captured target company number"),
+ company
+ .books_from
+ .clone()
+ .expect("captured target company books-from"),
+ &companies,
+ )
+ .expect("captured target company tuple must remain uniquely admissible")
+}
+
+fn captured_company_identity() -> VerifiedCompanyIdentity {
+ identity_from_company_extent(COMPANY_EXTENT_V2)
+}
+
+#[test]
+fn party_master_snapshot_uses_the_next_common_admissible_boundary() {
+ let period = party_ledger_master_balance_period(
+ DateBoundaryProfile::ModeAgnostic,
+ TallyDate::parse("20260401").unwrap(),
+ TallyDate::parse("20260415").unwrap(),
+ )
+ .expect("the derived common boundary is valid");
+ assert_eq!(period.to().as_str(), "20260501");
+ assert!(period.to() >= &TallyDate::parse("20260415").unwrap());
+}
+
+#[test]
+fn party_master_opening_balance_comparison_accepts_equivalent_decimal_scale() {
+ assert!(party_ledger_master_openings_agree(
+ "0",
+ &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
+ )
+ .unwrap());
+}
+
+#[test]
+fn party_master_opening_balance_comparison_withholds_a_real_numeric_mismatch() {
+ assert!(!party_ledger_master_openings_agree(
+ "0.01",
+ &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
+ )
+ .unwrap());
+}
+
+#[test]
+fn party_master_opening_balance_comparison_rejects_an_unparseable_master_value() {
+ assert!(party_ledger_master_openings_agree(
+ "not-an-amount",
+ &bridge_tally_core::ExactDecimal::parse("0.00").unwrap(),
+ )
+ .is_err());
+}
+
+fn utf16_xml_response(body: impl AsRef) -> Vec {
+ let body = bridge_tally_protocol::encode_tally_xml_request_utf16le(body.as_ref());
+ let headers = format!(
+ "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-16\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
+ body.len()
+ );
+ [headers.as_bytes(), &body].concat()
+}
+
+fn utf8_status_response(body: impl AsRef) -> Vec {
+ let body = body.as_ref().as_bytes();
+ let headers = format!(
+ "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
+ body.len()
+ );
+ [headers.as_bytes(), body].concat()
+}
+
+async fn read_complete_http_request(socket: &mut (impl tokio::io::AsyncRead + Unpin)) -> Vec {
+ const HEADER_TERMINATOR: &[u8] = b"\r\n\r\n";
+ const MAX_TEST_HEADER_BYTES: usize = 64 * 1024;
+
+ let mut request = Vec::new();
+ let mut chunk = [0_u8; 4096];
+ let header_end = loop {
+ if let Some(offset) = request
+ .windows(HEADER_TERMINATOR.len())
+ .position(|window| window == HEADER_TERMINATOR)
+ {
+ break offset + HEADER_TERMINATOR.len();
+ }
+ let bytes_read = socket
+ .read(&mut chunk)
+ .await
+ .expect("read synthetic HTTP request headers");
+ assert!(
+ bytes_read > 0,
+ "synthetic HTTP request ended before its headers were complete"
+ );
+ request.extend_from_slice(&chunk[..bytes_read]);
+ assert!(
+ request.len() <= MAX_TEST_HEADER_BYTES,
+ "synthetic HTTP request headers exceeded {MAX_TEST_HEADER_BYTES} bytes"
+ );
+ };
+
+ let headers = std::str::from_utf8(&request[..header_end - HEADER_TERMINATOR.len()])
+ .expect("synthetic HTTP request headers must be UTF-8");
+ let mut lines = headers.split("\r\n");
+ let request_line = lines
+ .next()
+ .expect("synthetic HTTP request must contain a request line");
+ let mut request_line_parts = request_line.split_ascii_whitespace();
+ let method = request_line_parts
+ .next()
+ .expect("synthetic HTTP request method is missing");
+ request_line_parts
+ .next()
+ .expect("synthetic HTTP request target is missing");
+ let version = request_line_parts
+ .next()
+ .expect("synthetic HTTP request version is missing");
+ assert_eq!(version, "HTTP/1.1", "synthetic request must use HTTP/1.1");
+ assert!(
+ request_line_parts.next().is_none(),
+ "synthetic HTTP request line has unexpected fields"
+ );
+
+ let mut content_length = None;
+ for header in lines {
+ let (name, value) = header
+ .split_once(':')
+ .expect("synthetic HTTP request contains a malformed header");
+ assert!(
+ !name.eq_ignore_ascii_case("transfer-encoding"),
+ "synthetic HTTP request must use Content-Length framing"
+ );
+ if name.eq_ignore_ascii_case("content-length") {
+ assert!(
+ content_length.is_none(),
+ "synthetic HTTP request contains duplicate Content-Length headers"
+ );
+ content_length = Some(
+ value
+ .trim()
+ .parse::()
+ .expect("synthetic HTTP request has an invalid Content-Length"),
+ );
+ }
+ }
+ let content_length = match content_length {
+ Some(length) => length,
+ None if method == "GET" || method == "HEAD" => 0,
+ None => panic!("synthetic HTTP request body is missing Content-Length"),
+ };
+ let request_end = header_end
+ .checked_add(content_length)
+ .expect("synthetic HTTP request length overflowed usize");
+ assert!(
+ request.len() <= request_end,
+ "synthetic HTTP request contains bytes beyond Content-Length"
+ );
+
+ while request.len() < request_end {
+ let remaining = request_end - request.len();
+ let chunk_length = remaining.min(chunk.len());
+ let bytes_read = socket
+ .read(&mut chunk[..chunk_length])
+ .await
+ .expect("read synthetic HTTP request body");
+ assert!(
+ bytes_read > 0,
+ "synthetic HTTP request ended before its Content-Length body was complete"
+ );
+ request.extend_from_slice(&chunk[..bytes_read]);
+ }
+
+ request
+}
+
+fn assert_company_collection_request_shape(request: &str) {
+ assert!(request.contains("Collection"));
+ for method in ["NAME", "GUID", "PRODUCTNAME"] {
+ assert!(request.contains(&format!("{method}")));
+ }
+ for expression in [
+ "EduMode : $$LicenseInfo:IsEducationalMode",
+ "Silver : $$LicenseInfo:IsSilver",
+ "Gold : $$LicenseInfo:IsGold",
+ ] {
+ assert!(request.contains(expression));
+ }
+}
+
+#[test]
+fn detects_tallyprime_status() {
+ assert!(matches!(
+ detect_product("TallyPrime Server is Running"),
+ TallyProduct::TallyPrime
+ ));
+}
+
+#[test]
+fn detects_erp9_status() {
+ assert!(matches!(
+ detect_product("Tally ERP 9 Server is Running"),
+ TallyProduct::TallyErp9
+ ));
+}
+
+#[test]
+fn product_marker_is_not_accepted_inside_unrelated_content() {
+ assert!(matches!(
+ detect_product("TallyPrime Server is Running"),
+ TallyProduct::Unknown
+ ));
+ assert!(matches!(
+ detect_product("prefix Tally ERP 9 Server is Running suffix"),
+ TallyProduct::Unknown
+ ));
+}
+
+#[tokio::test]
+async fn selected_read_request_digest_matches_the_dispatched_wire_entity() {
+ let plan = ScenarioPlan::new(Fixture::NormalExport).with_encoding(WireEncoding::Utf16Le);
+ let simulator = Simulator::spawn(plan).expect("spawn synthetic Tally endpoint");
+ let client = TallyClient::new(TallyConfig {
+ host: simulator.address().ip().to_string(),
+ port: simulator.address().port(),
+ })
+ .expect("build synthetic Tally client");
+
+ let observation = client
+ .qualify_selected_ledgers(
+ "BRIDGE SYNTHETIC BOOK",
+ "00000000-0000-4000-8000-000000000001",
+ )
+ .await
+ .expect("qualify synthetic selected-ledger read");
+ let dispatched = simulator.finish().expect("finish synthetic Tally exchange");
+
+ assert_eq!(observation.request_sha256, dispatched.request_body_sha256);
+}
+
+#[test]
+fn company_identity_normalization_rejects_invalid_and_ambiguous_guids() {
+ let normalized = normalize_discovered_companies(vec![
+ crate::tally::TallyCompany {
+ name: " Synthetic A ".to_string(),
+ guid: Some(" GUID-1 ".to_string()),
+ company_number: Some("100005".to_string()),
+ books_from: Some("20250401".to_string()),
+ },
+ crate::tally::TallyCompany {
+ name: "Synthetic B".to_string(),
+ guid: Some("guid-1".to_string()),
+ company_number: Some("100014".to_string()),
+ books_from: Some("20260401".to_string()),
+ },
+ ])
+ .expect("normalize company identities");
+ assert_eq!(normalized[0].name, "Synthetic A");
+ assert_eq!(normalized[0].guid.as_deref(), Some("GUID-1"));
+ assert!(unique_company_identities(&normalized));
+ assert!(!has_presentation_equivalent_guid_siblings(&normalized));
+
+ assert!(
+ normalize_discovered_companies(vec![crate::tally::TallyCompany {
+ name: "Synthetic\nCompany".to_string(),
+ guid: Some("guid-2".to_string()),
+ company_number: None,
+ books_from: None,
+ }])
+ .is_err()
+ );
+ assert!(
+ normalize_discovered_companies(vec![crate::tally::TallyCompany {
+ name: "Synthetic Company".to_string(),
+ guid: Some("guid\n2".to_string()),
+ company_number: None,
+ books_from: None,
+ }])
+ .is_err()
+ );
+}
+
+#[test]
+fn presentation_equivalent_guid_siblings_are_not_stable_company_identities() {
+ let companies = vec![
+ crate::tally::TallyCompany {
+ name: "Synthetic Company".to_string(),
+ guid: Some("guid-3".to_string()),
+ company_number: Some("100005".to_string()),
+ books_from: Some("20250401".to_string()),
+ },
+ crate::tally::TallyCompany {
+ name: " synthetic company ".to_string(),
+ guid: Some("GUID-3".to_string()),
+ company_number: Some("100014".to_string()),
+ books_from: Some("20260401".to_string()),
+ },
+ ];
+
+ assert!(unique_company_identities(&companies));
+ assert!(has_presentation_equivalent_guid_siblings(&companies));
+}
+
+#[test]
+fn identical_name_same_guid_distinct_books_are_not_stable_company_identities() {
+ let companies = vec![
+ crate::tally::TallyCompany {
+ name: "Synthetic Company".to_string(),
+ guid: Some("guid-3".to_string()),
+ company_number: Some("100005".to_string()),
+ books_from: Some("20250401".to_string()),
+ },
+ crate::tally::TallyCompany {
+ name: "Synthetic Company".to_string(),
+ guid: Some("GUID-3".to_string()),
+ company_number: Some("100014".to_string()),
+ books_from: Some("20260401".to_string()),
+ },
+ ];
+
+ assert!(unique_company_identities(&companies));
+ assert!(has_presentation_equivalent_guid_siblings(&companies));
+}
+
+#[test]
+fn validates_tally_endpoint_components() {
+ assert_eq!(
+ tally_endpoint(&TallyConfig::default(), "/status")
+ .expect("localhost endpoint")
+ .as_str(),
+ "http://127.0.0.1:9000/status"
+ );
+ let config = TallyConfig {
+ host: "::1".to_string(),
+ port: 9000,
+ };
+ assert_eq!(
+ tally_endpoint(&config, "/status")
+ .expect("IPv6 endpoint")
+ .as_str(),
+ "http://[::1]:9000/status"
+ );
+ for host in ["localhost", "127.0.0.1"] {
+ assert_eq!(
+ canonical_loopback_origin(&TallyConfig {
+ host: host.to_string(),
+ port: 9000,
+ })
+ .expect("canonical loopback origin"),
+ "http://127.0.0.1:9000"
+ );
+ }
+ assert_eq!(
+ canonical_loopback_origin(&TallyConfig {
+ host: "::1".to_string(),
+ port: 9000,
+ })
+ .expect("canonical IPv6 loopback origin"),
+ "http://[::1]:9000"
+ );
+
+ for host in ["http://localhost", "localhost/path", "user@localhost", ""] {
+ let invalid = TallyConfig {
+ host: host.to_string(),
+ port: 9000,
+ };
+ assert!(tally_endpoint(&invalid, "/status").is_err());
+ }
+
+ for host in [
+ "192.168.1.10",
+ "10.0.0.5",
+ "169.254.1.1",
+ "224.0.0.1",
+ "8.8.8.8",
+ "tally.internal",
+ ] {
+ let remote = TallyConfig {
+ host: host.to_string(),
+ port: 9000,
+ };
+ assert!(tally_endpoint(&remote, "/status").is_err());
+ }
+}
+
+#[test]
+fn decodes_supported_xml_byte_order_marks_and_rejects_invalid_sequences() {
+ let utf8 = [b"\xEF\xBB\xBF".as_slice(), b""].concat();
+ assert_eq!(decode_xml_bytes(utf8).expect("UTF-8 BOM"), "");
+
+ let document = "नमस्ते";
+ let mut utf16le = vec![0xFF, 0xFE];
+ utf16le.extend(document.encode_utf16().flat_map(u16::to_le_bytes));
+ assert_eq!(decode_xml_bytes(utf16le).expect("UTF-16LE"), document);
+
+ let mut utf16be = vec![0xFE, 0xFF];
+ utf16be.extend(document.encode_utf16().flat_map(u16::to_be_bytes));
+ assert_eq!(decode_xml_bytes(utf16be).expect("UTF-16BE"), document);
+
+ assert!(decode_xml_bytes(vec![0xFF, 0xFE, 0x00]).is_err());
+ assert!(decode_xml_bytes(vec![0x80]).is_err());
+}
+
+#[tokio::test]
+async fn tally_requests_ignore_configured_proxy() {
+ let tally_listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let tally_address = tally_listener.local_addr().expect("Tally address");
+ let proxy_listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic proxy");
+ let proxy_address = proxy_listener.local_addr().expect("proxy address");
+
+ let tally_server = tokio::spawn(async move {
+ let accepted = tokio::time::timeout(Duration::from_secs(2), tally_listener.accept())
+ .await
+ .expect("Tally request timed out")
+ .expect("accept Tally request");
+ let (mut socket, _) = accepted;
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ String::from_utf8_lossy(&request).starts_with("GET /status HTTP/1.1"),
+ "request should go directly to the Tally endpoint"
+ );
+ let body = "TallyPrime Server is Running";
+ socket
+ .write_all(&utf8_status_response(body))
+ .await
+ .expect("write Tally response");
+ });
+
+ let proxy_server = tokio::spawn(async move {
+ match tokio::time::timeout(Duration::from_millis(750), proxy_listener.accept()).await {
+ Ok(Ok((mut socket, _))) => {
+ let response =
+ "HTTP/1.1 502 Bad Gateway\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
+ socket
+ .write_all(response.as_bytes())
+ .await
+ .expect("write proxy response");
+ true
+ }
+ Ok(Err(error)) => panic!("accept proxy request: {error}"),
+ Err(_) => false,
+ }
+ });
+
+ let client = TallyClient::with_http_builder(
+ TallyConfig {
+ host: tally_address.ip().to_string(),
+ port: tally_address.port(),
+ },
+ reqwest::Client::builder().proxy(
+ reqwest::Proxy::all(format!("http://{proxy_address}")).expect("synthetic proxy URL"),
+ ),
+ );
+
+ let status = client
+ .check_connection()
+ .await
+ .expect("check synthetic Tally connection");
+ tally_server.await.expect("synthetic Tally server task");
+ let proxy_received_request = proxy_server.await.expect("synthetic proxy task");
+
+ assert!(status.reachable, "direct Tally response should be accepted");
+ assert!(
+ status.compatible,
+ "synthetic Tally status should be recognized"
+ );
+ assert!(
+ !proxy_received_request,
+ "Tally traffic must never be sent through a configured proxy"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn paired_outstandings_reads_health_check_between_and_after_requests() {
+ const OPTIONAL_VOUCHERS: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/unit_a_optional_voucher_live.xml"
+ );
+ const STATUS: &str = "TallyPrime Server is Running";
+ let identity = captured_company_identity();
+
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ for (index, expected_prefix) in [
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ "POST / HTTP/1.1",
+ "GET /status HTTP/1.1",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .expect("paired request timed out")
+ .expect("accept paired request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected_prefix),
+ "request {index} did not follow the required read/health-check sequence"
+ );
+ let body = match index {
+ 0 | 2 | 12 | 14 => COMPANY_EXTENT_V2,
+ 4 | 6 | 8 | 10 => OPTIONAL_VOUCHERS,
+ _ => STATUS,
+ };
+ let response = if index % 2 == 0 {
+ utf16_xml_response(body)
+ } else {
+ utf8_status_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write paired response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let extent = client
+ .fetch_company_book_extent(&identity)
+ .await
+ .expect("paired extent reads remain stable");
+ let reporting_window = bridge_tally_protocol::outstandings::DateWindow::parse(
+ bridge_tally_protocol::outstandings::DateBoundaryProfile::ModeAgnostic,
+ "20260401",
+ "20260401",
+ )
+ .expect("synthetic one-day window");
+ let segment_window = reporting_window
+ .narrow_partitions()
+ .expect("one narrow partition")
+ .remove(0);
+ let observation = client
+ .fetch_outstandings_segment_pair(
+ extent.company(),
+ segment_window.clone(),
+ bridge_tally_protocol::outstandings::AlterIdRange::new(0, 101603)
+ .expect("non-empty synthetic range"),
+ )
+ .await
+ .expect("paired segment reads remain stable");
+ let verification = observation.verification;
+ let bridge_tally_protocol::outstandings::SegmentVerification::Complete(segment) = verification
+ else {
+ panic!("captured voucher response did not verify: {verification:?}");
+ };
+ assert!(
+ !segment.vouchers().is_empty(),
+ "captured voucher fixture unexpectedly had no vouchers"
+ );
+ let witness = client
+ .fetch_empty_partition_witness_pair(extent.company(), segment_window)
+ .await
+ .expect("paired empty-date witness reads remain stable");
+ let bridge_tally_protocol::outstandings::WitnessPairVerification::Complete(witness) = witness
+ else {
+ panic!("captured witness fixture did not verify: {witness:?}");
+ };
+ assert!(
+ !witness.vouchers().is_empty(),
+ "captured witness fixture unexpectedly had no identity rows"
+ );
+ let closing_extent = client
+ .fetch_company_book_extent(&identity)
+ .await
+ .expect("closing paired extent reads remain stable");
+ assert_eq!(closing_extent, extent, "synthetic book did not change");
+ assert_eq!(
+ client.observed_body_bytes(),
+ Some(
+ u64::try_from(
+ bridge_tally_protocol::encode_tally_xml_request_utf16le(OPTIONAL_VOUCHERS).len(),
+ )
+ .expect("encoded fixture length fits u64"),
+ ),
+ "closing extent evidence must not overwrite the larger voucher payload"
+ );
+ server.await.expect("synthetic Tally server task");
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn paired_ledger_opening_coverage_reports_intra_pair_drift() {
+ const STATUS: &str = "TallyPrime Server is Running";
+ let identity = captured_company_identity();
+
+ let coverage = |name: &str| {
+ format!(
+ "bb8ad19e-6aef-4239-a917-87fec0c6215e-00000001Yes0"
+ )
+ };
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let responses = vec![
+ COMPANY_EXTENT_V2.to_string(),
+ STATUS.to_string(),
+ COMPANY_EXTENT_V2.to_string(),
+ STATUS.to_string(),
+ coverage("Before Rename"),
+ STATUS.to_string(),
+ coverage("After Rename"),
+ STATUS.to_string(),
+ ];
+ let server = tokio::spawn(async move {
+ for (index, body) in responses.into_iter().enumerate() {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .expect("paired request timed out")
+ .expect("accept paired request");
+ let request = read_complete_http_request(&mut socket).await;
+ let expected_prefix = if index % 2 == 0 {
+ "POST / HTTP/1.1"
+ } else {
+ "GET /status HTTP/1.1"
+ };
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected_prefix),
+ "request {index} did not follow the required read/health-check sequence"
+ );
+ let response = if index % 2 == 0 {
+ utf16_xml_response(body)
+ } else {
+ utf8_status_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write paired response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let extent = client
+ .fetch_company_book_extent(&identity)
+ .await
+ .expect("paired extent reads remain stable");
+ assert!(matches!(
+ client
+ .fetch_ledger_opening_coverage(extent.company())
+ .await
+ .expect("coverage responses parse"),
+ LedgerOpeningCoverageRead::Drifted
+ ));
+ server.await.expect("synthetic Tally server task");
+}
+
+/// The outstandings bracket (`fetch_company_book_extent`, feeding both
+/// `fetch_outstandings_native` and `fetch_ledgers`) must fail closed with
+/// a typed error when both paired reads agree but neither carries
+/// `ALTMSTID`. This is the exact case the review flagged: two
+/// witness-less extents compare equal, so the ordinary `first != second`
+/// drift check alone cannot tell a stable book from one where a
+/// GROUP/LEDGER master moved mid-window without a signal to detect it.
+/// Uses only a targeted mutation of the captured V2 response. The
+/// production parser must continue refusing a stable pair without the
+/// master witness instead of accepting a fabricated positive fixture.
+#[tokio::test]
+async fn outstandings_bracket_fails_closed_when_altmstid_is_absent() {
+ const STATUS: &str = "TallyPrime Server is Running";
+ const CAPTURED_TARGET_WITNESS: &str = concat!(
+ "bb8ad19e-6aef-4239-a917-87fec0c6215e\r\n",
+ " 100000\r\n",
+ " 101605\r\n",
+ " 328"
+ );
+ const CAPTURED_TARGET_WITHOUT_WITNESS: &str = concat!(
+ "bb8ad19e-6aef-4239-a917-87fec0c6215e\r\n",
+ " 100000\r\n",
+ " 101605"
+ );
+ let identity = captured_company_identity();
+ assert_eq!(
+ COMPANY_EXTENT_V2.matches(CAPTURED_TARGET_WITNESS).count(),
+ 1,
+ "the exact captured target witness must appear once before mutation"
+ );
+ let company_extent_without_altmstid =
+ COMPANY_EXTENT_V2.replacen(CAPTURED_TARGET_WITNESS, CAPTURED_TARGET_WITHOUT_WITNESS, 1);
+ assert!(
+ !company_extent_without_altmstid.contains(CAPTURED_TARGET_WITNESS),
+ "the captured target witness mutation must apply"
+ );
+ let parsed_without_witness =
+ bridge_tally_protocol::outstandings_shared::parse_company_book_extent_v2(
+ &company_extent_without_altmstid,
+ &identity
+ .company_book_extent_expectation()
+ .expect("captured expectation"),
+ )
+ .expect("the captured negative mutation must remain a parseable extent");
+ assert!(
+ parsed_without_witness
+ .master_alter_id_high_water()
+ .is_none(),
+ "the target witness mutation must remove only the target ALTMSTID"
+ );
+
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let responses = [
+ company_extent_without_altmstid.clone(),
+ STATUS.to_string(),
+ company_extent_without_altmstid,
+ STATUS.to_string(),
+ ];
+ let server = tokio::spawn(async move {
+ for (index, body) in responses.into_iter().enumerate() {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .expect("paired request timed out")
+ .expect("accept paired request");
+ let request = read_complete_http_request(&mut socket).await;
+ let expected_prefix = if index % 2 == 0 {
+ "POST / HTTP/1.1"
+ } else {
+ "GET /status HTTP/1.1"
+ };
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected_prefix),
+ "request {index} did not follow the required read/health-check sequence"
+ );
+ let response = if index % 2 == 0 {
+ utf16_xml_response(&body)
+ } else {
+ utf8_status_response(&body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write paired response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let error = client
+ .fetch_company_book_extent(&identity)
+ .await
+ .expect_err("a stable pair without ALTMSTID must still fail closed");
+ server.await.expect("synthetic Tally server task");
+ assert!(
+ error
+ .downcast_ref::()
+ .is_some_and(|error| {
+ *error
+ == bridge_tally_protocol::outstandings_shared::OutstandingsError::MasterWitnessAbsent
+ }),
+ "unexpected error: {error:#}"
+ );
+}
+
+#[tokio::test]
+async fn http_success_with_tally_status_zero_is_not_an_empty_success() {
+ let identity = captured_company_identity();
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ for (index, body) in [
+ COMPANY_EXTENT_V2,
+ "TallyPrime Server is Running",
+ COMPANY_EXTENT_V2,
+ "TallyPrime Server is Running",
+ "",
+ "TallyPrime Server is Running",
+ "",
+ "TallyPrime Server is Running",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ let expected = if index == 1 || index == 3 || index == 5 || index == 7 {
+ "GET /status HTTP/1.1"
+ } else {
+ "POST / HTTP/1.1"
+ };
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected),
+ "ledger fetch did not preserve its extent/read sequence"
+ );
+ if index == 4 || index == 6 {
+ let body_start = request
+ .windows(4)
+ .position(|window| window == b"\r\n\r\n")
+ .map(|offset| offset + 4)
+ .expect("native ledger POST has complete HTTP headers");
+ let request_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &request[body_start..],
+ request.len(),
+ )
+ .expect("native ledger POST uses decodable UTF-16 XML")
+ .text;
+ assert!(request_xml.contains(r#"20240401"#));
+ assert!(request_xml.contains(r#"20260401"#));
+ }
+ let response = if index == 1 || index == 3 || index == 5 || index == 7 {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let error = client
+ .fetch_ledgers(&identity, DateBoundaryProfile::ModeAgnostic)
+ .await
+ .expect_err("STATUS 0 must not become an empty ledger result");
+ server.await.expect("synthetic Tally server task");
+ assert!(
+ error
+ .to_string()
+ .contains("native ledger collection did not report success"),
+ "unexpected error: {error:#}"
+ );
+}
+
+#[tokio::test]
+async fn invalid_book_extent_stops_ledger_export_without_a_date_fallback() {
+ let identity = captured_company_identity();
+ let invalid_extent =
+ COMPANY_EXTENT_V2.replacen(r#"20240401"#, "", 1);
+ assert_ne!(invalid_extent, COMPANY_EXTENT_V2);
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ for (index, body) in [
+ invalid_extent.as_str(),
+ "TallyPrime Server is Running",
+ invalid_extent.as_str(),
+ "TallyPrime Server is Running",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept extent request");
+ let request = read_complete_http_request(&mut socket).await;
+ let expected = if index % 2 == 0 {
+ "POST / HTTP/1.1"
+ } else {
+ "GET /status HTTP/1.1"
+ };
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected),
+ "request {index} did not follow the paired extent sequence"
+ );
+ let response = if index % 2 == 0 {
+ utf16_xml_response(body)
+ } else {
+ utf8_status_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write extent response");
+ }
+ assert!(
+ tokio::time::timeout(Duration::from_millis(200), listener.accept())
+ .await
+ .is_err(),
+ "an invalid extent must stop before any native ledger request"
+ );
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ client
+ .fetch_ledgers(&identity, DateBoundaryProfile::ModeAgnostic)
+ .await
+ .expect_err("missing BOOKSFROM must fail closed");
+ server.await.expect("synthetic Tally server task");
+}
+
+#[tokio::test]
+async fn education_profile_rejects_an_unsupported_books_from_before_ledger_export() {
+ let extent = COMPANY_EXTENT_V2.replacen(
+ r#"20240401"#,
+ r#"20240115"#,
+ 1,
+ );
+ let identity = identity_from_company_extent(&extent);
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ for (index, body) in [
+ extent.as_str(),
+ "TallyPrime Server is Running",
+ extent.as_str(),
+ "TallyPrime Server is Running",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept extent request");
+ let request = read_complete_http_request(&mut socket).await;
+ let expected = if index % 2 == 0 {
+ "POST / HTTP/1.1"
+ } else {
+ "GET /status HTTP/1.1"
+ };
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected),
+ "request {index} did not follow the paired extent sequence"
+ );
+ let response = if index % 2 == 0 {
+ utf16_xml_response(body)
+ } else {
+ utf8_status_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write extent response");
+ }
+ assert!(
+ tokio::time::timeout(Duration::from_millis(200), listener.accept())
+ .await
+ .is_err(),
+ "an Education-invalid BOOKSFROM must stop before the native ledger request"
+ );
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let error = client
+ .fetch_ledgers(&identity, DateBoundaryProfile::EducationRestricted)
+ .await
+ .expect_err("unsupported boundary must not reach the ledger export");
+ server.await.expect("synthetic Tally server task");
+ assert!(
+ error
+ .to_string()
+ .contains("not supported by the endpoint compatibility profile"),
+ "unexpected error: {error:#}"
+ );
+}
+
+fn native_voucher_collection_xml(rows: &[&str]) -> String {
+ format!(
+ "{}",
+ rows.join("")
+ )
+}
+
+const SYNTHETIC_VOUCHER_ROW: &str = r#"20260401synthetic-company-guid-0000000111PaymentNoNoCash-100.00Yes"#;
+
+/// A native Voucher collection carries no envelope company GUID, and a
+/// zero-row response has no per-row GUID either -- there is nothing for
+/// `parse_native_voucher_source_records_with_evidence` to bind. Before
+/// the fix, `fetch_vouchers` accepted such a response unauthenticated,
+/// so a silently substituted or dropped company looked identical to a
+/// genuinely empty window. This reproduces that: the voucher read comes
+/// back empty, and the out-of-band book-extent bracket that should
+/// confirm the pinned company instead observes a different one.
+#[tokio::test]
+async fn empty_voucher_response_is_rejected_when_pinned_company_cannot_be_confirmed() {
+ let identity = captured_company_identity();
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let empty_vouchers = native_voucher_collection_xml(&[]);
+ let substituted_extent =
+ COMPANY_EXTENT_V2.replacen(CAPTURED_COMPANY_GUID, "substituted-company-guid", 1);
+ let status = "TallyPrime Server is Running";
+ let steps: Vec<(&str, String, bool)> = vec![
+ ("POST / HTTP/1.1", empty_vouchers, false),
+ ("POST / HTTP/1.1", substituted_extent.clone(), false),
+ ("GET /status HTTP/1.1", status.to_string(), true),
+ ("POST / HTTP/1.1", substituted_extent, false),
+ ("GET /status HTTP/1.1", status.to_string(), true),
+ ];
+ let server = tokio::spawn(async move {
+ for (index, (expected_prefix, body, is_status)) in steps.into_iter().enumerate() {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .unwrap_or_else(|_| {
+ panic!(
+ "request {index} timed out -- the empty-voucher rejection path \
+ did not attempt to confirm the pinned company out of band"
+ )
+ })
+ .expect("accept synthetic Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected_prefix),
+ "request {index} did not follow the voucher-then-extent-bracket sequence"
+ );
+ let response = if is_status {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write synthetic Tally response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let error = client
+ .fetch_vouchers(&identity, "20260401", "20260401")
+ .await
+ .expect_err(
+ "an empty voucher response must not be accepted when the pinned company book \
+ extent cannot be confirmed",
+ );
+ server.await.expect("synthetic Tally server task");
+ assert!(
+ error
+ .to_string()
+ .contains("empty voucher response could not confirm the pinned company book extent"),
+ "unexpected error: {error:#}"
+ );
+}
+
+/// Same empty voucher response as above, but this time the out-of-band
+/// book-extent bracket confirms the pinned company is still selected and
+/// stable -- so the empty result is accepted.
+#[tokio::test]
+async fn empty_voucher_response_is_accepted_when_bracket_confirms_pinned_company() {
+ let identity = captured_company_identity();
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let empty_vouchers = native_voucher_collection_xml(&[]);
+ let confirmed_extent = COMPANY_EXTENT_V2.to_string();
+ let status = "TallyPrime Server is Running";
+ let steps: Vec<(&str, String, bool)> = vec![
+ ("POST / HTTP/1.1", empty_vouchers, false),
+ ("POST / HTTP/1.1", confirmed_extent.clone(), false),
+ ("GET /status HTTP/1.1", status.to_string(), true),
+ ("POST / HTTP/1.1", confirmed_extent, false),
+ ("GET /status HTTP/1.1", status.to_string(), true),
+ ];
+ let request_count = steps.len();
+ let server = tokio::spawn(async move {
+ for (index, (expected_prefix, body, is_status)) in steps.into_iter().enumerate() {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .unwrap_or_else(|_| panic!("request {index} timed out"))
+ .expect("accept synthetic Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ String::from_utf8_lossy(&request).starts_with(expected_prefix),
+ "request {index} did not follow the voucher-then-extent-bracket sequence"
+ );
+ let response = if is_status {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write synthetic Tally response");
+ }
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let vouchers = client
+ .fetch_vouchers(&identity, "20260401", "20260401")
+ .await
+ .expect("an empty voucher response confirmed by the extent bracket must be accepted");
+ server.await.expect("synthetic Tally server task");
+ assert!(vouchers.is_empty());
+ assert_eq!(
+ request_count, 5,
+ "the empty path must pay for exactly one voucher read plus the extent bracket"
+ );
+}
+
+/// A non-empty voucher response keeps its existing row-GUID binding and
+/// must not pay for the extent bracket -- the common case stays a single
+/// request.
+#[tokio::test]
+async fn non_empty_voucher_response_issues_no_extra_request() {
+ let identity = captured_company_identity();
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let voucher_row =
+ SYNTHETIC_VOUCHER_ROW.replace("synthetic-company-guid", CAPTURED_COMPANY_GUID);
+ let non_empty_vouchers = native_voucher_collection_xml(&[&voucher_row]);
+
+ let server = tokio::spawn(async move {
+ let (mut socket, _) = tokio::time::timeout(Duration::from_secs(2), listener.accept())
+ .await
+ .expect("voucher request timed out")
+ .expect("accept synthetic Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ String::from_utf8_lossy(&request).starts_with("POST / HTTP/1.1"),
+ "unexpected request for the non-empty voucher read"
+ );
+ socket
+ .write_all(&utf16_xml_response(non_empty_vouchers))
+ .await
+ .expect("write synthetic Tally response");
+
+ // A non-empty response must not pay for the extent bracket: no
+ // further connection should ever arrive.
+ let extra = tokio::time::timeout(Duration::from_millis(300), listener.accept()).await;
+ assert!(
+ extra.is_err(),
+ "non-empty voucher fetch issued an unexpected extra request"
+ );
+ });
+
+ let client = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client");
+ let vouchers = client
+ .fetch_vouchers(&identity, "20260401", "20260401")
+ .await
+ .expect("non-empty voucher fetch must still succeed exactly as today");
+ server.await.expect("synthetic Tally server task");
+ assert_eq!(vouchers.len(), 1);
+}
+
+#[tokio::test]
+async fn capability_probe_reports_only_observed_xml_support() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ for (index, body) in [
+ "LOCAL STATUS HEURISTIC UNRECOGNIZED",
+ "guid-110000120260401TallyPrimeNoYesNo",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(!request.is_empty(), "synthetic Tally request must not be empty");
+ let response = if index == 0 {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let probe = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .probe()
+ .await
+ .expect("probe synthetic Tally endpoint");
+ server.await.expect("synthetic Tally server task");
+
+ assert!(probe.connection.reachable);
+ assert!(!probe.connection.compatible);
+ assert_eq!(probe.companies.len(), 1);
+ assert_eq!(
+ probe.profile.transports[&TransportId::XmlHttp].state,
+ CapabilityState::Supported
+ );
+ assert_eq!(
+ probe.profile.packs[&CapabilityPackId::CoreAccounting].state,
+ CapabilityState::Unknown
+ );
+ assert_eq!(probe.profile.product, "TallyPrime");
+ assert!(probe.profile.release.is_none());
+ assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
+ assert_eq!(probe.profile.profile_version, 4);
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::ProductAndMode].state,
+ CapabilityState::Supported
+ );
+ let boundary = crate::tally::TallyRuntime::default()
+ .master_ledger_export_boundary_profile_from_profile(Some(&probe.profile));
+ assert_eq!(boundary, DateBoundaryProfile::ModeAgnostic);
+ assert!(NativeLedgerExportPeriod::new(
+ boundary,
+ TallyDate::parse("20240115").expect("valid mid-month date"),
+ TallyDate::parse("20240115").expect("valid mid-month date"),
+ )
+ .is_ok());
+ for transport in [TransportId::TdlCompanion, TransportId::Odbc] {
+ let evidence = &probe.profile.transports[&transport];
+ assert_eq!(evidence.state, CapabilityState::Unknown);
+ assert_eq!(evidence.confidence, EvidenceConfidence::Unknown);
+ assert_eq!(
+ evidence.safe_reason_code.as_deref(),
+ Some("configuration_not_observed")
+ );
+ }
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::EndpointReachability].state,
+ CapabilityState::Supported
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::LoadedCompanies].state,
+ CapabilityState::Supported
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
+ CapabilityState::Supported
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::EncodingBehaviour]
+ .safe_reason_code
+ .as_deref(),
+ Some("utf16_le_bom_observed")
+ );
+ for feature in [
+ CapabilityFeatureId::PracticalResponseLimit,
+ CapabilityFeatureId::LedgerRead,
+ CapabilityFeatureId::VoucherRead,
+ CapabilityFeatureId::Write,
+ ] {
+ assert_eq!(
+ probe.profile.features[&feature].state,
+ CapabilityState::Unknown
+ );
+ }
+}
+
+#[tokio::test]
+async fn capability_probe_records_unavailable_product_mode_evidence_without_refusing() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ let responses = [
+ utf8_status_response("TallyPrime Server is Running"),
+ utf16_xml_response(
+ "Capability collection unavailable",
+ ),
+ utf16_xml_response(
+ "Synthetic Companyguid-1",
+ ),
+ ];
+ for response in responses {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ !request.is_empty(),
+ "synthetic Tally request must not be empty"
+ );
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let probe = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .probe()
+ .await
+ .expect("unavailable product/mode evidence must not refuse the probe");
+ server.await.expect("synthetic Tally server task");
+
+ assert_eq!(probe.profile.product, "Unknown");
+ assert!(probe.profile.mode.is_none());
+ assert_eq!(
+ crate::tally::TallyRuntime::default()
+ .master_ledger_export_boundary_profile_from_profile(Some(&probe.profile)),
+ DateBoundaryProfile::ModeAgnostic
+ );
+ let evidence = &probe.profile.features[&CapabilityFeatureId::ProductAndMode];
+ assert_eq!(evidence.state, CapabilityState::Unknown);
+ assert_eq!(evidence.confidence, EvidenceConfidence::Observed);
+ assert_eq!(
+ evidence.safe_reason_code.as_deref(),
+ Some("product_mode_evidence_unavailable")
+ );
+}
+
+/// `fetch_companies` now requests the native `Company` collection
+/// (`ReadOnlyProfile::CompanyListV2`) instead of the legacy `CompanyListV1`
+/// custom TDL report. This asserts the request itself carries that shape --
+/// `TYPE=Collection`, no `REPORT`/`FORM`/`PART`/`LINE`/`FIELD` stack, and no
+/// `SVCURRENTCOMPANY` scoping a discovery read to one company -- and that a
+/// company row's `NAME` attribute and nested `GUID` are trimmed and returned.
+#[tokio::test]
+async fn interactive_company_fetch_sends_the_native_collection_request() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ let body = r#" guid-1 "#;
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ let body_start = request
+ .windows(4)
+ .position(|window| window == b"\r\n\r\n")
+ .map(|position| position + 4)
+ .expect("POST request has complete HTTP headers");
+ let post_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &request[body_start..],
+ request.len(),
+ )
+ .expect("POST request uses decodable UTF-16 XML")
+ .text;
+ assert_company_collection_request_shape(&post_xml);
+ assert!(!post_xml.contains(""#;
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(
+ !request.is_empty(),
+ "synthetic Tally request must not be empty"
+ );
+ socket
+ .write_all(&utf16_xml_response(body))
+ .await
+ .expect("write Tally response");
+ });
+
+ let error = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .fetch_companies()
+ .await
+ .expect_err("a company row without a GUID must fail closed");
+ server.await.expect("synthetic Tally server task");
+ assert!(error.to_string().contains("GUID"));
+ let retained = error
+ .downcast_ref::()
+ .expect("parser refusal retains its typed wire-evidence boundary");
+ assert!(retained.evidence.bytes > 0);
+}
+
+#[tokio::test]
+async fn direct_company_bootstrap_uses_only_the_shaped_collection_identity() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ // `fetch_companies` (the first of the two reads below) now requests the
+ // native `Company` collection (`ReadOnlyProfile::CompanyListV2`), so its
+ // response carries that shape rather than the legacy `CompanyListV1`
+ // direct report. Its GUID must still not escape into the returned
+ // identity -- only the second, scoped `standard` read may do that.
+ let discovered = r#"scoped-guid10000120260401"#;
+ let standard = "ledger-guidPrimaryscoped-guidSynthetic Company1033";
+ let server = tokio::spawn(async move {
+ for body in [discovered, standard] {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(!request.is_empty());
+ socket
+ .write_all(&utf16_xml_response(body))
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let company = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .bootstrap_direct_company("Synthetic Company")
+ .await
+ .expect("strict scoped bootstrap should succeed");
+ server.await.expect("synthetic Tally server task");
+
+ assert_eq!(company.name, "Synthetic Company");
+ assert_eq!(company.guid.as_deref(), Some("scoped-guid"));
+ assert_eq!(company.company_number.as_deref(), Some("100001"));
+ assert_eq!(company.books_from.as_deref(), Some("20260401"));
+}
+
+#[tokio::test]
+async fn capability_probe_does_not_promote_a_direct_company_report() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ // This responder gives the same untrusted bare direct company
+ // report regardless of what is requested: once for the `V2`
+ // collection attempt (which the collection parser rejects, since
+ // it never satisfies `HEADER/STATUS`), and once more for the
+ // `V1` fallback that follows.
+ for (index, body) in [
+ "LOCAL STATUS HEURISTIC UNRECOGNIZED",
+ "Synthetic Companyguid-1",
+ "Synthetic Companyguid-1",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(!request.is_empty(), "synthetic Tally request must not be empty");
+ let response = if index == 0 {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let probe = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .probe()
+ .await
+ .expect("probe synthetic Tally endpoint");
+ server.await.expect("synthetic Tally server task");
+
+ assert!(probe.connection.reachable);
+ assert!(probe.companies.is_empty());
+ assert_eq!(
+ probe.profile.transports[&TransportId::XmlHttp].state,
+ CapabilityState::Unknown
+ );
+ assert_eq!(
+ probe.profile.transports[&TransportId::XmlHttp]
+ .safe_reason_code
+ .as_deref(),
+ Some("direct_company_report_untrusted")
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::CompanyRead].state,
+ CapabilityState::Unknown
+ );
+}
+
+#[tokio::test]
+async fn capability_probe_does_not_promote_a_shaped_company_failure_to_xml_support() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ // Same shaped `STATUS=0` failure for both the `V2` collection
+ // attempt and the `V1` fallback that follows it.
+ for (index, body) in [
+ "TallyPrime Server is Running",
+ "Could not find Company ''",
+ "Could not find Company ''",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ assert!(!request.is_empty(), "synthetic Tally request must not be empty");
+ let response = if index == 0 {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ });
+
+ let probe = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .probe()
+ .await
+ .expect("probe synthetic Tally endpoint");
+ server.await.expect("synthetic Tally server task");
+
+ assert!(probe.companies.is_empty());
+ let xml = &probe.profile.transports[&TransportId::XmlHttp];
+ assert_eq!(xml.state, CapabilityState::Unknown);
+ assert_eq!(xml.confidence, EvidenceConfidence::Observed);
+ assert_eq!(xml.safe_reason_code.as_deref(), Some("company_not_loaded"));
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::LoadedCompanies].state,
+ CapabilityState::Unknown
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
+ CapabilityState::Unknown
+ );
+}
+
+/// A gateway-shaped `Company` collection response with two loaded
+/// synthetic companies and the `CMPINFO` counter trap included. Proves
+/// `probe` requests `CompanyListV2` on the happy path
+/// and trusts its success without ever falling back to the legacy
+/// `CompanyListV1` report: the mock server has exactly one POST response
+/// queued, so a fallback request would hang and fail this test.
+#[tokio::test]
+async fn capability_probe_marks_presentation_equivalent_guid_siblings_ambiguous() {
+ let listener = TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic Tally server");
+ let address = listener.local_addr().expect("synthetic Tally address");
+ let server = tokio::spawn(async move {
+ let mut requests = Vec::new();
+ for (index, body) in [
+ "TallyPrime Server is Running",
+ "\n \n 0\n \n Synthetic Company Asynthetic-guid-a10000120260401TallyPrimeNoYesNo\n synthetic company a SYNTHETIC-GUID-A10000220270401TallyPrimeNoYesNo\n \n \n",
+ ]
+ .into_iter()
+ .enumerate()
+ {
+ let (mut socket, _) = listener.accept().await.expect("accept Tally request");
+ let request = read_complete_http_request(&mut socket).await;
+ requests.push(request);
+ let response = if index == 0 {
+ utf8_status_response(body)
+ } else {
+ utf16_xml_response(body)
+ };
+ socket
+ .write_all(&response)
+ .await
+ .expect("write Tally response");
+ }
+ requests
+ });
+
+ let probe = TallyClient::new(TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ })
+ .expect("build synthetic Tally client")
+ .probe()
+ .await
+ .expect("probe synthetic Tally endpoint");
+ let requests = server.await.expect("synthetic Tally server task");
+
+ assert_eq!(probe.companies.len(), 2);
+ assert_eq!(probe.companies[0].name, "Synthetic Company A");
+ assert_eq!(probe.companies[0].guid.as_deref(), Some("synthetic-guid-a"));
+ assert_eq!(probe.companies[1].name, "synthetic company a");
+ assert_eq!(probe.companies[1].guid.as_deref(), Some("SYNTHETIC-GUID-A"));
+ assert_eq!(probe.profile.product, "TallyPrime");
+ assert_eq!(probe.profile.mode.as_deref(), Some("Licensed"));
+ assert_eq!(
+ probe.profile.transports[&TransportId::XmlHttp].state,
+ CapabilityState::Supported
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity]
+ .safe_reason_code
+ .as_deref(),
+ Some("company_identity_display_scope_ambiguous")
+ );
+ assert_eq!(
+ probe.profile.features[&CapabilityFeatureId::StableCompanyIdentity].state,
+ CapabilityState::Unknown
+ );
+
+ let post_request = requests
+ .iter()
+ .find(|request| request.starts_with(b"POST"))
+ .expect("exactly one POST request was sent");
+ let body_start = post_request
+ .windows(4)
+ .position(|window| window == b"\r\n\r\n")
+ .map(|position| position + 4)
+ .expect("POST request has complete HTTP headers");
+ let post_xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &post_request[body_start..],
+ post_request.len(),
+ )
+ .expect("POST request uses decodable UTF-16 XML");
+ assert_company_collection_request_shape(&post_xml.text);
+ assert!(post_xml.text.contains("BridgeCompanyExtent"));
+}
diff --git a/src-tauri/src/tally/runtime.rs b/src-tauri/src/tally/runtime.rs
index e35fb68c..7eddfa24 100644
--- a/src-tauri/src/tally/runtime.rs
+++ b/src-tauri/src/tally/runtime.rs
@@ -3486,1956 +3486,5 @@ fn map_execution_error(error: ReadExecutionError) -> anyhow::Erro
}
#[cfg(test)]
-mod tests {
- use super::*;
- use crate::commands::VerifiedCompanyIdentity;
- use crate::tally::TallyProduct;
- use anyhow::Context;
- use bridge_tally_core::CapabilityProfile;
- use std::collections::BTreeMap;
- use tally_protocol_simulator::Fixture;
- use tokio::io::{AsyncReadExt, AsyncWriteExt};
-
- fn verified_identity(name: &str, guid: &str) -> VerifiedCompanyIdentity {
- VerifiedCompanyIdentity::test_fixture(name, guid)
- }
-
- fn captured_aarav_company_list_and_identity() -> (String, VerifiedCompanyIdentity) {
- let bytes = include_bytes!("../../crates/bridge-tally-protocol/tests/fixtures/agent/native-licensed-release-companies.utf16le.xml");
- let xml = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
- bytes,
- "text/xml; charset=utf-16",
- bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
- bytes.len(),
- )
- .expect("captured Company collection decodes")
- .text;
- let rows = parse_companies_from_collection(&xml).unwrap();
- let row = rows
- .iter()
- .find(|row| row.name == "Aarav Trading Company Demo")
- .unwrap();
- let identity = VerifiedCompanyIdentity::from_observed_companies(
- row.name.clone(),
- row.guid.clone().unwrap(),
- row.company_number.clone().unwrap(),
- row.books_from.clone().unwrap(),
- &rows,
- )
- .unwrap();
- (xml, identity)
- }
-
- #[test]
- fn agent_company_list_evidence_hashes_the_encoded_utf16_response_bytes() {
- let response = "g-1120260401".to_string();
- let encoded = bridge_tally_protocol::encode_tally_xml_request_utf16le(&response);
- let expected_bytes = encoded.len();
- let expected_sha = Sha256::digest(&encoded)
- .iter()
- .map(|byte| format!("{byte:02x}"))
- .collect::();
- let listed =
- agent_company_list_from_response(response, expected_bytes, expected_sha.clone())
- .expect("company list");
- assert_eq!(listed.response_bytes, expected_bytes);
- assert_eq!(listed.response_sha256, expected_sha);
- assert_eq!(listed.companies.len(), 1);
- }
-
- #[test]
- fn party_ledger_master_evidence_includes_currency_probe_and_source_responses() {
- let source = |master_response_sha256: &str| PartyLedgerMasterSource {
- company: "Synthetic Books".to_string(),
- company_guid: "company-guid".to_string(),
- currency_assertion: OutstandingsCurrencyAssertion::Inr,
- currency_decimal_places: 2,
- from: TallyDate::parse("20260401").expect("source from"),
- to: TallyDate::parse("20260731").expect("source to"),
- rows: Vec::new(),
- request_sha256: "0".repeat(64),
- master_response_sha256: master_response_sha256.to_string(),
- balance_response_sha256: "b".repeat(64),
- group_response_sha256: "c".repeat(64),
- master_response_bytes: 11,
- balance_response_bytes: 13,
- group_response_bytes: 17,
- groups: Vec::new(),
- };
- let currency =
- RuntimeReadEvidence::paired("", sha256_hex(b"currency-response"), 19);
- let baseline = TallyRuntime::party_ledger_master_source_evidence(
- &source(&"a".repeat(64)),
- currency.clone(),
- );
- let changed = TallyRuntime::party_ledger_master_source_evidence(
- &source(&"d".repeat(64)),
- currency.clone(),
- );
- let changed_currency = TallyRuntime::party_ledger_master_source_evidence(
- &source(&"a".repeat(64)),
- RuntimeReadEvidence::paired(
- "",
- sha256_hex(b"changed-currency-response"),
- 23,
- ),
- );
- let source_scope = "0".repeat(64);
- let source_responses = sha256_hex(
- format!("{}:{}:{}", "a".repeat(64), "b".repeat(64), "c".repeat(64)).as_bytes(),
- );
- assert_eq!(
- baseline.request_sha256,
- sha256_hex(format!("{}:{source_scope}", currency.request_sha256).as_bytes())
- );
- assert_eq!(
- baseline.response_sha256,
- sha256_hex(format!("{}:{source_responses}", currency.response_sha256).as_bytes())
- );
- assert_eq!(baseline.request_sha256, changed_currency.request_sha256);
- assert_ne!(baseline.response_sha256, changed_currency.response_sha256);
- assert_eq!(changed_currency.bytes, (11 + 13 + 17 + 23) * 2);
-
- assert_eq!(baseline.request_sha256, changed.request_sha256);
- assert_ne!(baseline.response_sha256, changed.response_sha256);
- assert_eq!(baseline.bytes, (11 + 13 + 17 + 19) * 2);
- }
-
- #[test]
- fn paired_evidence_commits_to_utf16le_request_body_not_utf8_source() {
- let request = "₹ & Co";
- let mut encoded = vec![0xff, 0xfe];
- encoded.extend(request.encode_utf16().flat_map(u16::to_le_bytes));
- let evidence = RuntimeReadEvidence::paired(request, "r".repeat(64), 7);
- assert_eq!(evidence.request_sha256, sha256_hex(&encoded));
- assert_ne!(evidence.request_sha256, sha256_hex(request.as_bytes()));
- assert_eq!(evidence.bytes, 14);
- }
-
- #[test]
- fn outstandings_wire_evidence_changes_with_any_native_report_response() {
- let receivable = RuntimeReadEvidence::paired("", sha256_hex(b"r1"), 3);
- let payable = RuntimeReadEvidence::paired("", sha256_hex(b"p1"), 5);
- let baseline = receivable.clone().combine(payable.clone());
- let changed = receivable.combine(RuntimeReadEvidence::paired(
- "",
- sha256_hex(b"p2"),
- 5,
- ));
-
- assert_eq!(baseline.bytes, 16, "both paired responses are counted");
- assert_ne!(baseline.response_sha256, changed.response_sha256);
- assert_ne!(baseline.request_sha256, String::new());
- }
-
- fn utf16_xml_response(body: impl AsRef) -> Vec {
- let body = bridge_tally_protocol::encode_tally_xml_request_utf16le(body.as_ref());
- utf16_xml_response_bytes(&body)
- }
-
- fn utf16_xml_response_bytes(body: &[u8]) -> Vec {
- let headers = format!(
- "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-16\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
- body.len()
- );
- [headers.as_bytes(), body].concat()
- }
-
- fn utf8_status_response(body: impl AsRef) -> Vec {
- let body = body.as_ref().as_bytes();
- let headers = format!(
- "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
- body.len()
- );
- [headers.as_bytes(), body].concat()
- }
-
- async fn read_http_request(socket: &mut tokio::net::TcpStream) -> Vec {
- let mut request = Vec::new();
- let mut buffer = [0_u8; 4096];
- let (header_end, content_length) = loop {
- let read = socket.read(&mut buffer).await.expect("read request");
- assert!(read > 0, "request ended before headers completed");
- request.extend_from_slice(&buffer[..read]);
- let Some(header_end) = request.windows(4).position(|bytes| bytes == b"\r\n\r\n") else {
- continue;
- };
- let header = std::str::from_utf8(&request[..header_end])
- .expect("request headers are valid UTF-8");
- let content_length = header
- .lines()
- .find_map(|line| {
- let (name, value) = line.split_once(':')?;
- name.eq_ignore_ascii_case("content-length")
- .then_some(value.trim())
- })
- .map(|value| {
- value
- .parse::()
- .expect("request Content-Length is numeric")
- })
- .unwrap_or(0);
- break (header_end + 4, content_length);
- };
- while request.len() < header_end + content_length {
- let read = socket.read(&mut buffer).await.expect("read request body");
- assert!(read > 0, "request ended before declared body completed");
- request.extend_from_slice(&buffer[..read]);
- }
- request
- }
-
- #[test]
- fn ageing_anchor_serializes_as_an_explicit_wire_contract() {
- assert_eq!(
- serde_json::to_value(OutstandingsAgeingAnchor::DueDate).unwrap(),
- serde_json::json!("due_date")
- );
- assert_eq!(
- serde_json::to_value(OutstandingsAgeingAnchor::BillDate).unwrap(),
- serde_json::json!("bill_date")
- );
- }
-
- #[test]
- fn outstandings_read_strategy_serializes_as_an_explicit_wire_contract() {
- assert_eq!(
- serde_json::to_value(OutstandingsReadStrategy::NativeBills).unwrap(),
- serde_json::json!("native_bills")
- );
- assert_eq!(
- serde_json::to_value(OutstandingsReadStrategy::VoucherScan).unwrap(),
- serde_json::json!("voucher_scan")
- );
- }
-
- #[test]
- fn single_company_forex_ledger_capture_returns_a_typed_partial() {
- const FOREX_LEDGER_CAPTURE: &[u8] = include_bytes!(
- "../../crates/bridge-tally-protocol/tests/fixtures/ledgers_forex_composite_live.utf16le.xml"
- );
- let ledger_body = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
- FOREX_LEDGER_CAPTURE,
- "text/xml; charset=utf-16",
- bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
- FOREX_LEDGER_CAPTURE.len(),
- )
- .expect("captured forex ledger response decodes")
- .text;
- let admitted = admit_native_ledger_snapshot(
- parse_native_ledger_snapshot(&ledger_body)
- .map_err(anyhow::Error::from)
- .context("stable native ledger snapshot"),
- )
- .expect("a foreign-currency ledger is an in-band partial");
-
- assert!(matches!(
- admitted,
- NativeLedgerSnapshotAdmission::Partial(OutstandingsLoadResult::Partial { reason, .. })
- if reason.reason_code == "company_foreign_currency_ledger_balance"
- && reason.foreign_currency_ledger_name.as_deref() == Some("FX USD Debtor 02")
- ));
- }
-
- #[tokio::test]
- async fn single_company_read_returns_the_forex_capture_partial() {
- const EXTENT: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
- );
- const FOREX_LEDGER_CAPTURE: &[u8] = include_bytes!(
- "../../crates/bridge-tally-protocol/tests/fixtures/ledgers_forex_composite_live.utf16le.xml"
- );
- const RECEIVABLE: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_aarav.xml"
- );
- const PAYABLE: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_payable_aarav.xml"
- );
- const STATUS: &str = "TallyPrime Server is Running";
- let (company_list, identity) = captured_aarav_company_list_and_identity();
-
- let extent = EXTENT;
-
- let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic outstandings server");
- let address = listener.local_addr().expect("synthetic server address");
- let server = tokio::spawn(async move {
- let mut source_post_index = 0;
- let mut company_post_index = 0;
- for index in 0..30 {
- let (mut socket, _) =
- tokio::time::timeout(std::time::Duration::from_secs(2), listener.accept())
- .await
- .expect("outstandings request timed out")
- .expect("accept outstandings request");
- let request = read_http_request(&mut socket).await;
- let response = if request.starts_with(b"GET /status") {
- utf8_status_response(STATUS)
- } else {
- assert!(request.starts_with(b"POST /"), "request {index}");
- let body_start = request
- .windows(4)
- .position(|bytes| bytes == b"\r\n\r\n")
- .expect("complete request headers")
- + 4;
- let xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
- &request[body_start..],
- request.len() - body_start,
- )
- .expect("decode request")
- .text;
- if xml.contains("BridgeCompanyExtent")
- && !xml.contains("")
- {
- company_post_index += 1;
- utf16_xml_response(&company_list)
- } else {
- let response = match source_post_index {
- 0 | 1 | 10 | 11 => utf16_xml_response(extent),
- 2 | 3 => utf16_xml_response(RECEIVABLE),
- 6 | 7 => utf16_xml_response(PAYABLE),
- _ => utf16_xml_response_bytes(FOREX_LEDGER_CAPTURE),
- };
- source_post_index += 1;
- response
- }
- };
- socket.write_all(&response).await.expect("write response");
- }
- assert_eq!(company_post_index, 4);
- assert_eq!(source_post_index, 12);
- });
-
- let result = TallyRuntime::default()
- .fetch_outstandings(
- TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- },
- &identity,
- TallyDate::parse("20260401").expect("captured book as-of"),
- OutstandingsCurrencyAssertion::Inr,
- OutstandingsAgeingAnchor::DueDate,
- )
- .await
- .expect("foreign-currency capture returns an in-band partial");
-
- assert!(matches!(
- result,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "company_foreign_currency_ledger_balance"
- && reason.foreign_currency_ledger_name.as_deref() == Some("FX USD Debtor 02")
- ));
- server.await.expect("synthetic outstandings server task");
- }
-
- #[test]
- fn validation_lab_future_bill_stays_unaged_in_open_bill_output() {
- let receivable = parse_native_bill_rows(
- include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_validation_lab.xml"
- ),
- &TallyDate::parse("20250401").expect("captured BooksFrom"),
- &TallyDate::parse("20260817").expect("capture as-of"),
- )
- .expect("captured validation-book rows parse");
- let rows = all_open_bill_rows(
- &receivable,
- &[],
- OutstandingsAgeingAnchor::DueDate,
- &TallyDate::parse("20260817").expect("capture as-of"),
- );
- let future = rows
- .iter()
- .find(|row| row.reference == "ALPHA-FUTURE")
- .expect("captured future-due bill remains present");
- assert_eq!(future.amount.as_str(), "22222.00");
- assert_eq!(future.age_days, None);
-
- let bill_date_rows = all_open_bill_rows(
- &receivable,
- &[],
- OutstandingsAgeingAnchor::BillDate,
- &TallyDate::parse("20260817").expect("capture as-of"),
- );
- let bill_date_future = bill_date_rows
- .iter()
- .find(|row| row.reference == "ALPHA-FUTURE")
- .expect("captured future-due bill remains present for bill-date ageing");
- assert!(
- bill_date_future.age_days.is_some(),
- "the selected bill-date basis must not reuse the future due date"
- );
- }
-
- #[test]
- fn settled_native_bill_rows_do_not_reach_statement_or_export_sources() {
- let books_from = TallyDate::parse("20250401").expect("synthetic BooksFrom");
- let as_of = TallyDate::parse("20260817").expect("synthetic as-of");
- let parsed = parse_native_bill_rows(
- "1-Aug-26SETTLED\
- Synthetic Customer0.00\
- 1-Aug-2616",
- &books_from,
- &as_of,
- )
- .expect("synthetic settled row parses at the wire boundary");
-
- assert_eq!(parsed.len(), 1, "the fixture proves the native row existed");
- assert!(
- all_open_bill_rows(&parsed, &[], OutstandingsAgeingAnchor::DueDate, &as_of).is_empty(),
- "a zero closing balance is not an open bill"
- );
- }
-
- #[test]
- fn captured_validation_lab_bytes_render_a_complete_working_paper_end_to_end() {
- const COMPANY: &str = "Bridge Validation Lab";
- const COMPANY_GUID: &str = "c6afd306-00e1-4f51-802a-babe44daddd3";
- let books_from = TallyDate::parse("20250401").expect("captured BooksFrom");
- let as_of = TallyDate::parse("20260801").expect("captured as-of date");
- let receivable_xml = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_validation_lab.xml"
- );
- let payable_xml = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_payable_validation_lab.xml"
- );
- let groups_xml = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/group_snapshot_validation_lab.xml"
- );
- let ledgers_xml = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/native/ledger_snapshot_validation_lab.xml"
- );
-
- let receivable = parse_native_bill_rows(receivable_xml, &books_from, &as_of)
- .expect("captured receivable rows parse");
- let payable = parse_native_bill_rows(payable_xml, &books_from, &as_of)
- .expect("captured payable rows parse");
- // This captured Group body predates the request's response-bound
- // compute. Add only that collection context for the parser contract;
- // source-byte accounting below remains the exact captured response.
- let groups_with_response_company_guid = groups_xml.replace(
- "",
- "c6afd306-00e1-4f51-802a-babe44daddd3",
- );
- let groups = parse_native_group_snapshot(&groups_with_response_company_guid, COMPANY_GUID)
- .expect("captured group identity and ancestry parse");
- let ledgers =
- parse_native_ledger_snapshot(ledgers_xml).expect("captured ledger controls parse");
- let source_bytes = receivable_xml
- .len()
- .checked_add(payable_xml.len())
- .and_then(|value| value.checked_add(groups_xml.len()))
- .and_then(|value| value.checked_add(ledgers_xml.len()))
- .expect("captured source byte count fits usize");
- let computed = compute_native_outstandings(
- COMPANY,
- &receivable,
- &payable,
- NativeMasterSnapshot {
- ledgers: &ledgers,
- groups: NativeGroupSnapshot::Complete(&groups),
- },
- NativeAgeingAnchor::DueDate,
- &as_of,
- source_bytes,
- )
- .expect("captured native controls compute");
- assert_eq!(
- native_crosscheck_partial_reason(&computed, &as_of),
- None,
- "captured overdue counters must positively prove the requested date"
- );
-
- let statement_open_bills = all_open_bill_rows(
- &receivable,
- &payable,
- OutstandingsAgeingAnchor::DueDate,
- &as_of,
- );
- assert_eq!(statement_open_bills.len(), 6);
- assert!(statement_open_bills.iter().all(|row| !row.amount.is_zero()));
- let statement_unallocated_by_party = all_unallocated_parties(&computed.residuals);
- let result = OutstandingsLoadResult::Complete {
- report: Box::new(computed.report),
- read_strategy: OutstandingsReadStrategy::NativeBills,
- currency_assertion: OutstandingsCurrencyAssertion::Inr,
- ageing_anchor: OutstandingsAgeingAnchor::DueDate,
- synced_at_unix_ms: 1_777_000_000_000,
- unallocated_total: Some(computed.residual_total),
- statement_unallocated_by_party,
- statement_open_bills,
- };
- let source = crate::reports::outstandings_working_paper_store::source_from_complete_result(
- &result,
- COMPANY_GUID,
- )
- .expect("captured source stays inside export budgets")
- .expect("captured complete result substantiates a source");
- assert_eq!(source.source_bytes, 33_575);
- assert_eq!(source.open_bills.len(), 6);
- let paper =
- crate::reports::outstandings_working_paper::build_outstandings_working_paper(source)
- .expect("captured exact controls reconcile into a working paper");
- let workbook = crate::reports::outstandings_working_paper_xlsx::render_outstandings_working_paper_xlsx(&paper)
- .expect("captured working paper renders");
- assert!(workbook.len() > 200);
- assert_eq!(&workbook[0..2], b"PK");
- }
-
- #[test]
- fn raw_overdue_disagreement_withholds_native_complete() {
- let rows = parse_native_bill_rows(
- "1-Jul-26MISMATCH\
- Synthetic Customer-100.00\
- 1-Jul-2631",
- &TallyDate::parse("20250401").expect("synthetic BooksFrom"),
- &TallyDate::parse("20260817").expect("synthetic as-of"),
- )
- .expect("raw bill parses");
- let computed = compute_native_outstandings(
- "Synthetic Company",
- &rows,
- &[],
- NativeMasterSnapshot {
- ledgers: &[],
- groups: NativeGroupSnapshot::LegacyFixtureWithoutGroups,
- },
- NativeAgeingAnchor::DueDate,
- &TallyDate::parse("20260817").expect("synthetic as-of"),
- 0,
- )
- .expect("arithmetic still computes for diagnostic comparison");
- assert_eq!(
- computed.overdue_crosscheck,
- NativeOverdueCrosscheck::Inconsistent
- );
- assert_eq!(
- native_crosscheck_partial_reason(&computed, &TallyDate::parse("20260817").unwrap()),
- Some(OutstandingsPartialReason::code(
- "native_overdue_crosscheck_mismatch"
- ))
- );
- }
-
- #[test]
- fn zero_bill_rows_with_a_ledger_residual_withhold_native_complete() {
- let groups = parse_native_group_snapshot(
- r#"
- 11111111-1111-1111-1111-111111111111-0000000111111111-1111-1111-1111-111111111111Primary
- "#,
- "11111111-1111-1111-1111-111111111111",
- )
- .expect("synthetic group snapshot parses");
- let ledgers = parse_native_ledger_snapshot(
- r#"
- Sundry Debtors-100.000No
- "#,
- )
- .expect("synthetic ledger snapshot parses");
- let requested_as_of = TallyDate::parse("20260817").expect("synthetic as-of");
- let computed = compute_native_outstandings(
- "Synthetic Company",
- &[],
- &[],
- NativeMasterSnapshot {
- ledgers: &ledgers,
- groups: NativeGroupSnapshot::Complete(&groups),
- },
- NativeAgeingAnchor::DueDate,
- &requested_as_of,
- 0,
- )
- .expect("the residual is a partial-result diagnostic, not a parser failure");
-
- assert_eq!(
- computed.overdue_crosscheck,
- NativeOverdueCrosscheck::UnconfirmedAsOfWithoutBillReferences
- );
- assert_eq!(
- native_crosscheck_partial_reason(&computed, &requested_as_of),
- Some(OutstandingsPartialReason::code(
- WarningCode::NativeOutstandingsAsOfUnconfirmedWithoutBillReferences.as_str(),
- ))
- );
- }
-
- #[test]
- fn raw_empty_bill_references_preserve_each_amount_and_get_explicit_label() {
- let raw_bills = "\
- 1-Jul-26Synthetic Customer\
- -40.001-Jul-2630\
- 2-Jul-26 \n\t Synthetic Customer\
- -60.002-Jul-2629\
- ";
- let as_of = TallyDate::parse("20260731").expect("synthetic as-of");
- let parsed = parse_native_bill_rows(
- raw_bills,
- &TallyDate::parse("20250401").expect("synthetic BooksFrom"),
- &as_of,
- )
- .expect("paired empty BILLREF values remain parseable");
- let rows = all_open_bill_rows(&parsed, &[], OutstandingsAgeingAnchor::DueDate, &as_of);
-
- assert_eq!(rows.len(), 2, "empty identities must not collapse rows");
- let total = rows
- .iter()
- .try_fold(ExactDecimal::zero(), |sum, row| {
- sum.checked_add(&row.amount)
- })
- .expect("synthetic bill total remains exact");
- assert_eq!(total.as_str(), "100", "neither amount may be lost");
- assert_eq!(
- rows.iter()
- .map(|row| row.reference.as_str())
- .collect::>(),
- vec!["No reference reported", "No reference reported"],
- "client-facing rows must disclose the missing identity",
- );
- }
-
- #[test]
- fn party_master_currency_assertion_rejects_a_changed_company_extent() {
- const EXTENT: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/unit_a_company_extent_live.xml"
- );
- let read_extent_xml = EXTENT.replacen(
- r#"bb8ad19e-6aef-4239-a917-87fec0c6215e"#,
- r#"bb8ad19e-6aef-4239-a917-87fec0c6215e1"#,
- 1,
- );
- let changed_extent_xml = read_extent_xml.replace(
- "20260401",
- "20260402",
- );
- let read_extent = bridge_tally_protocol::outstandings_shared::parse_company_book_extent(
- &read_extent_xml,
- "Aarav Trading Company Demo",
- "bb8ad19e-6aef-4239-a917-87fec0c6215e",
- )
- .expect("captured extent parses");
- let changed_extent = bridge_tally_protocol::outstandings_shared::parse_company_book_extent(
- &changed_extent_xml,
- "Aarav Trading Company Demo",
- "bb8ad19e-6aef-4239-a917-87fec0c6215e",
- )
- .expect("changed synthetic extent parses");
- let assertion = CompanyCurrencyRead {
- currency: CompanyCurrency {
- symbol: "₹".to_string(),
- mailing_name: "INR".to_string(),
- currency_count: 1,
- decimal_places: 2,
- is_inr: true,
- },
- extent: read_extent.clone(),
- evidence: RuntimeReadEvidence::empty(),
- }
- .bind_party_ledger_master_assertion(OutstandingsCurrencyAssertion::Inr);
-
- assert_eq!(
- assertion
- .require_opening_extent(&read_extent)
- .expect("the observed extent releases INR"),
- PartyLedgerMasterCurrency {
- assertion: OutstandingsCurrencyAssertion::Inr,
- decimal_places: 2,
- }
- );
- let error = assertion
- .require_opening_extent(&changed_extent)
- .expect_err("a changed extent must not release INR");
- assert!(matches!(
- error.downcast_ref::(),
- Some(PairedReadValidationError::CurrencyToMasterExtent)
- ));
- }
-
- #[tokio::test]
- async fn detect_base_currency_rejects_book_drift_after_the_currency_read() {
- const EXTENT: &str = include_str!(
- "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
- );
- const CURRENCY: &[u8] = include_bytes!(
- "../../crates/bridge-tally-protocol/tests/fixtures/currency_inr_modern_live.utf16le.xml"
- );
- const STATUS: &str = "TallyPrime Server is Running";
- let (company_list, identity) = captured_aarav_company_list_and_identity();
-
- let currency = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
- CURRENCY,
- "text/xml; charset=utf-16",
- bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
- CURRENCY.len(),
- )
- .expect("captured currency response decodes")
- .text;
-
- let opening_extent = EXTENT;
- let closing_extent = opening_extent.replace(
- "20260401",
- "20260402",
- );
- assert_ne!(
- closing_extent, opening_extent,
- "the drift mutation must apply"
- );
- let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind synthetic currency server");
- let address = listener.local_addr().expect("synthetic server address");
- let server = tokio::spawn(async move {
- let mut source_post_index = 0;
- for index in 0..13 {
- let (mut socket, _) =
- tokio::time::timeout(std::time::Duration::from_secs(2), listener.accept())
- .await
- .expect("currency request timed out")
- .expect("accept currency request");
- let request = read_http_request(&mut socket).await;
- let response = if request.starts_with(b"GET /status") {
- utf8_status_response(STATUS)
- } else {
- assert!(request.starts_with(b"POST /"), "request {index}");
- let body_start = request
- .windows(4)
- .position(|bytes| bytes == b"\r\n\r\n")
- .expect("complete request headers")
- + 4;
- let xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
- &request[body_start..],
- request.len() - body_start,
- )
- .expect("decode request")
- .text;
- if xml.contains("BridgeCompanyExtent")
- && !xml.contains("")
- {
- utf16_xml_response(&company_list)
- } else {
- let response = match source_post_index {
- 0 | 1 => utf16_xml_response(opening_extent),
- 2 | 3 => utf16_xml_response(¤cy),
- _ => utf16_xml_response(&closing_extent),
- };
- source_post_index += 1;
- response
- }
- };
- socket.write_all(&response).await.expect("write response");
- }
- });
-
- let runtime = TallyRuntime::default();
- let result = runtime
- .detect_base_currency(
- TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- },
- &identity,
- )
- .await;
-
- let error = result.expect_err("closing extent drift must reject the currency");
- assert!(
- error.chain().any(|cause| matches!(
- cause.downcast_ref::(),
- Some(PairedReadValidationError::CurrencyExtent)
- )),
- "unexpected error: {error:#}"
- );
- server.await.expect("synthetic currency server task");
- }
-
- fn synthetic_probe_result() -> TallyProbeResult {
- TallyProbeResult {
- connection: ConnectionStatus {
- reachable: true,
- compatible: false,
- server_text: "Synthetic status".to_string(),
- product: TallyProduct::Unknown,
- error: None,
- },
- companies: vec![TallyCompany {
- name: "Synthetic Company".to_string(),
- guid: Some("synthetic-guid".to_string()),
- company_number: None,
- books_from: None,
- }],
- profile: CapabilityProfile {
- profile_version: 2,
- product: "Unknown".to_string(),
- release: None,
- license_tier: None,
- mode: None,
- transports: BTreeMap::new(),
- features: BTreeMap::new(),
- packs: BTreeMap::new(),
- },
- selected_read_scope: None,
- passport_snapshot_id: None,
- }
- }
-
- #[test]
- fn future_due_bill_from_raw_native_bytes_reaches_the_statement_source() {
- let books_from = TallyDate::parse("20260401").expect("synthetic book start");
- let as_of = TallyDate::parse("20260731").expect("synthetic as-of date");
- let bills_xml = "\
- 1-Jul-26FUTURE-1Synthetic Party\
- -100.001-Aug-260\
- ";
- let ledger_xml = "";
- let receivable = parse_native_bill_rows(bills_xml, &books_from, &as_of)
- .expect("a future due date in raw native bytes must parse");
- let ledgers = parse_native_ledger_snapshot(ledger_xml)
- .expect("the synthetic raw ledger response must parse");
- let computed = compute_native_outstandings(
- "Synthetic Company",
- &receivable,
- &[],
- NativeMasterSnapshot {
- ledgers: &ledgers,
- groups: NativeGroupSnapshot::LegacyFixtureWithoutGroups,
- },
- NativeAgeingAnchor::DueDate,
- &as_of,
- bills_xml.len() + ledger_xml.len(),
- )
- .expect("a future-due bill must not abort the native computation");
- assert_eq!(computed.report.receivable_total.as_str(), "100");
- assert_eq!(computed.report.top_parties[0].oldest_bill_age_days, None);
- assert_eq!(computed.report.ageing.days_0_30.as_str(), "100");
- assert_eq!(computed.report.ageing.days_31_60, ExactDecimal::zero());
- assert_eq!(computed.report.ageing.days_61_90, ExactDecimal::zero());
- assert_eq!(computed.report.ageing.days_90_plus, ExactDecimal::zero());
- assert_eq!(computed.report.open_receivable_bill_count, 1);
-
- let statement_rows =
- all_open_bill_rows(&receivable, &[], OutstandingsAgeingAnchor::DueDate, &as_of);
- assert_eq!(
- statement_rows.len(),
- 1,
- "a future-due bill must remain available to the statement source"
- );
- assert_eq!(statement_rows[0].amount.as_str(), "100.00");
- assert_eq!(statement_rows[0].age_days, None);
- let statement = crate::reports::party_statement::build_party_statement(
- "Synthetic Company",
- as_of.as_str(),
- "Synthetic Party",
- &statement_rows,
- &[],
- )
- .expect("the future-due bill must build a party statement");
- assert_eq!(statement.bills.len(), 1);
- assert_eq!(statement.bills[0].reference, "FUTURE-1");
- assert_eq!(statement.bills[0].age_days, None);
- assert_eq!(statement.bills[0].bucket, None);
- assert_eq!(statement.bill_total.as_str(), "100");
-
- let destination = tempfile::tempdir().expect("synthetic destination");
- let approvals =
- crate::reports::bulk_party_statement::PartyStatementDestinationApprovals::default();
- let approval_id = approvals
- .issue(destination.path().to_path_buf())
- .expect("approve synthetic destination");
- let approved_destination = approvals
- .consume(&approval_id, destination.path())
- .expect("consume synthetic approval");
- let bulk = crate::reports::bulk_party_statement::write_bulk_party_statements(
- &approved_destination,
- "Synthetic Company",
- as_of.as_str(),
- "xlsx",
- &statement_rows,
- &[],
- |party_statement| {
- crate::reports::party_statement_xlsx::render_party_statement_xlsx(party_statement)
- .map_err(|error| error.to_string())
- },
- )
- .expect("a bulk run must write the future-due party statement");
- assert_eq!(bulk.written.len(), 1);
- let workbook = std::fs::File::open(destination.path().join(&bulk.written[0].file_name))
- .expect("the bulk statement file exists");
- let mut archive = zip::ZipArchive::new(workbook).expect("bulk output is an XLSX archive");
- let mut workbook_text = String::new();
- for entry_name in ["xl/worksheets/sheet1.xml", "xl/sharedStrings.xml"] {
- let mut entry = archive
- .by_name(entry_name)
- .expect("the XLSX statement entry exists");
- std::io::Read::read_to_string(&mut entry, &mut workbook_text)
- .expect("the workbook XML is readable");
- }
- assert!(workbook_text.contains("FUTURE-1"));
- assert!(workbook_text.contains("Not due"));
- assert!(workbook_text.contains("Unaged"));
- }
-
- #[test]
- fn education_snapshot_period_refusal_is_an_in_band_partial() {
- let result = match admit_native_ledger_snapshot_period(
- DateBoundaryProfile::EducationRestricted,
- TallyDate::parse("20260401").expect("Education-valid book start"),
- TallyDate::parse("20260415").expect("ordinary calendar as-of"),
- ) {
- NativeLedgerSnapshotPeriodAdmission::Period(_) => {
- panic!("an Education-refused as-of must not construct a snapshot period")
- }
- NativeLedgerSnapshotPeriodAdmission::Partial(partial) => partial,
- };
- assert!(matches!(
- result,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "as_of_has_no_valid_window_boundary"
- ));
- }
-
- #[cfg(feature = "voucher-scan")]
- #[test]
- fn outstandings_read_failures_are_partial_and_deadlines_recommend_restart() {
- assert_eq!(
- outstandings_read_failure_reason(&anyhow::Error::new(
- TallyTransportError::RequestTimedOut
- )),
- "tally_segment_deadline_restart_recommended"
- );
- assert_eq!(
- outstandings_read_failure_reason(&anyhow::Error::new(
- TallyTransportError::RequestFailed
- )),
- "segment_request_failed"
- );
- assert_eq!(
- outstandings_read_failure_reason(&anyhow::Error::new(
- TallyTransportError::HttpStatus { status: 500 }
- )),
- "segment_http_status_failure"
- );
- assert_eq!(
- outstandings_read_failure_reason(&anyhow::anyhow!("connection refused")),
- "segment_read_failed"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn outstandings_read_transport_failure_feeds_breaker_and_preserves_typed_partial() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9120,
- };
- let result = runtime
- .execute(
- config,
- ReadOperation::VoucherExport,
- ReadRetryPolicy::SINGLE_ATTEMPT,
- |_client| async {
- Err::(anyhow::Error::new(
- OutstandingsReadTransportFailure {
- reason_code: "tally_segment_deadline_restart_recommended",
- source: anyhow::Error::new(TallyTransportError::RequestTimedOut),
- },
- ))
- },
- )
- .await;
- let caller_value = partial_after_outstandings_read_transport_failure(result)
- .expect("outstandings read transport failures stay in-band for the caller");
- assert!(matches!(
- caller_value,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "tally_segment_deadline_restart_recommended"
- ));
- assert_eq!(
- runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
- 1,
- "the breaker must observe the transport failure before UI mapping"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn verification_partial_stays_partial_without_feeding_breaker() {
- let runtime = TallyRuntime::default();
- let result = runtime
- .execute(
- TallyConfig {
- host: "localhost".to_string(),
- port: 9121,
- },
- ReadOperation::VoucherExport,
- ReadRetryPolicy::SINGLE_ATTEMPT,
- |_client| async {
- Ok::<_, anyhow::Error>(partial_result("paired_segment_mismatch"))
- },
- )
- .await;
- let caller_value = partial_after_outstandings_read_transport_failure(result)
- .expect("verification partial remains an in-band result");
- assert!(matches!(
- caller_value,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "paired_segment_mismatch"
- ));
- assert_eq!(
- runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
- 0,
- "a verification failure reached a responder and must not poison endpoint health"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[test]
- fn closing_coverage_drift_is_not_reported_as_uncovered_opening_bills() {
- assert_eq!(
- closing_coverage_partial_reason(false, true),
- Some("ledger_master_identity_changed_during_scan")
- );
- assert_eq!(
- closing_coverage_partial_reason(true, false),
- Some("ledger_opening_bills_not_covered")
- );
- assert_eq!(closing_coverage_partial_reason(true, true), None);
- }
-
- #[cfg(feature = "voucher-scan")]
- #[test]
- fn intra_pair_ledger_coverage_drift_is_an_in_band_partial() {
- assert_eq!(
- paired_coverage_partial_reason(&LedgerOpeningCoverageRead::Drifted),
- Some("ledger_master_identity_changed_during_scan")
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn witness_transport_failure_feeds_breaker_and_preserves_typed_partial() {
- let error = fetch_empty_partition_witness(
- VoucherAlterIdHighWater::parse("1").expect("positive high-water"),
- || async { Err(anyhow::Error::new(TallyTransportError::RequestTimedOut)) },
- )
- .await
- .expect_err("a witness transport failure must cross the runtime health boundary");
- assert!(error.downcast_ref::().is_some());
-
- let runtime = TallyRuntime::default();
- let result = runtime
- .execute(
- TallyConfig {
- host: "localhost".to_string(),
- port: 9122,
- },
- ReadOperation::VoucherExport,
- ReadRetryPolicy::SINGLE_ATTEMPT,
- |_client| async {
- Err::(outstandings_read_transport_failure(
- anyhow::Error::new(TallyTransportError::RequestTimedOut),
- ))
- },
- )
- .await;
- let caller_value = partial_after_outstandings_read_transport_failure(result)
- .expect("witness transport failure stays in-band only after health accounting");
-
- assert!(matches!(
- caller_value,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "tally_segment_deadline_restart_recommended"
- ));
- assert_eq!(
- runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
- 1,
- "the breaker must observe the witness transport failure"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[tokio::test]
- async fn witness_non_transport_failure_remains_an_in_band_partial() {
- let partial = fetch_empty_partition_witness(
- VoucherAlterIdHighWater::parse("1").expect("positive high-water"),
- || async { Err(anyhow::anyhow!("witness response was malformed")) },
- )
- .await
- .expect("non-transport witness failure stays in-band")
- .expect_err("a malformed witness cannot complete a non-empty book");
-
- assert_eq!(
- partial.reason_code,
- "empty_date_witness_profile_unavailable"
- );
- }
-
- #[cfg(feature = "voucher-scan")]
- #[test]
- fn outstandings_date_boundaries_follow_detected_mode_and_fallback_to_i12() {
- let mut profile = synthetic_probe_result().profile;
- profile.product = "TallyPrime Edit Log".to_string();
- profile.mode = Some("Education".to_string());
- assert_eq!(
- select_date_boundary_profile(Some(&profile)),
- DateBoundaryProfile::EducationRestricted
- );
-
- profile.mode = Some("Licensed".to_string());
- assert_eq!(
- select_date_boundary_profile(Some(&profile)),
- DateBoundaryProfile::ModeAgnostic
- );
- assert_eq!(
- select_date_boundary_profile(None),
- DateBoundaryProfile::ModeAgnostic
- );
-
- profile.product = "Unknown".to_string();
- profile.mode = Some("Education".to_string());
- assert_eq!(
- select_date_boundary_profile(Some(&profile)),
- DateBoundaryProfile::ModeAgnostic,
- "inconsistent or incomplete detection must rely on I12 rather than inventing compatibility evidence"
- );
- }
-
- /// An uncalibrated segment width no longer refuses the read -- it selects
- /// the native bills path, which needs no width. What must NOT change is
- /// that a non-loopback endpoint is still refused.
- ///
- /// This test previously asserted `outstandings_segment_sizing_uncalibrated`
- /// and, by using a non-loopback host, proved the refusal happened BEFORE
- /// endpoint admission. That refusal was the defect: production has no
- /// calibrated width by construction, so every shipped build returned it for
- /// every company on every book and the screen could only ever say "No Tally
- /// data was read". The reason code is gone with it.
- ///
- /// The loopback guard is unchanged and still fails closed; it is simply now
- /// the first guard the native path reaches. That is the property worth
- /// pinning, so this asserts it directly rather than inferring it from an
- /// ordering that no longer exists.
- #[tokio::test]
- async fn uncalibrated_outstandings_takes_the_native_path_and_still_refuses_a_non_loopback_endpoint(
- ) {
- let runtime = TallyRuntime::default();
- #[cfg(feature = "voucher-scan")]
- assert!(
- runtime.outstandings_segment_policy.is_none(),
- "a default runtime must have no calibrated width -- that is what routes to the native path"
- );
-
- let error = runtime
- .fetch_outstandings(
- TallyConfig {
- host: "not-a-loopback-endpoint".to_string(),
- port: 9000,
- },
- &verified_identity("Synthetic Company", "synthetic-guid"),
- TallyDate::parse("20260731").unwrap(),
- OutstandingsCurrencyAssertion::Inr,
- OutstandingsAgeingAnchor::DueDate,
- )
- .await
- .expect_err("a non-loopback endpoint must never be contacted");
- assert!(
- error.to_string().contains("non_loopback_forbidden"),
- "loopback-only admission must still fail closed on the native path, got: {error}"
- );
- }
-
- #[cfg(feature = "live-calibration-harness")]
- #[tokio::test]
- async fn calibrated_voucher_scan_withholds_totals_before_endpoint_admission_without_residual_coverage(
- ) {
- let result = TallyRuntime::for_billwise_lab_reconciliation_exit_check()
- .fetch_outstandings(
- TallyConfig {
- host: "not-a-loopback-endpoint".to_string(),
- port: 9000,
- },
- &verified_identity("Synthetic Company", "synthetic-guid"),
- TallyDate::parse("20260731").unwrap(),
- OutstandingsCurrencyAssertion::Inr,
- OutstandingsAgeingAnchor::DueDate,
- )
- .await
- .expect("missing coverage is an in-band partial result");
- assert!(matches!(
- result,
- OutstandingsLoadResult::Partial { reason, .. }
- if reason.reason_code == "unallocated_direct_postings_not_covered"
- ));
- }
-
- #[cfg(all(feature = "voucher-scan", not(feature = "live-calibration-harness")))]
- #[test]
- fn default_build_has_no_outstandings_width_admission() {
- let runtime = TallyRuntime::default();
- assert!(runtime.outstandings_segment_policy.is_none());
- assert!(runtime.outstandings_boundary_profile_override.is_none());
- }
-
- #[cfg(feature = "live-calibration-harness")]
- #[test]
- fn billwise_lab_exit_harness_uses_only_education_valid_boundaries() {
- let runtime = TallyRuntime::for_billwise_lab_reconciliation_exit_check();
- assert_eq!(
- runtime.outstandings_boundary_profile_override,
- Some(DateBoundaryProfile::EducationRestricted)
- );
- let reporting_window = DateWindow::parse(
- runtime
- .outstandings_boundary_profile_override
- .expect("exit profile is fixed"),
- "20240401",
- "20260702",
- )
- .expect("accepted corpus extent uses Education-valid boundaries");
- let partitions = reporting_window
- .narrow_partitions()
- .expect("Education-valid corpus partitions without day-3 synthesis");
- assert!(partitions.iter().all(|partition| {
- matches!(&partition.from().as_str()[6..8], "01" | "02" | "31")
- && matches!(&partition.to().as_str()[6..8], "01" | "02" | "31")
- }));
-
- let unprofiled =
- DateWindow::parse(DateBoundaryProfile::ModeAgnostic, "20240401", "20260702")
- .unwrap()
- .narrow_partitions()
- .unwrap();
- assert!(unprofiled.iter().any(|partition| {
- !matches!(&partition.from().as_str()[6..8], "01" | "02" | "31")
- || !matches!(&partition.to().as_str()[6..8], "01" | "02" | "31")
- }));
- }
-
- #[test]
- fn local_rejections_and_client_http_statuses_do_not_poison_endpoint_health() {
- assert_eq!(
- classify_failure(&anyhow::Error::new(
- TallyRuntimeReadError::ApplicationResponseRejected,
- )),
- ReadFailureClass::Application
- );
- for error in [
- TallyTransportError::RequestTooLarge { limit: 1024 },
- TallyTransportError::PolicyInvalid { code: "test" },
- TallyTransportError::EndpointInvalid { code: "test" },
- TallyTransportError::ClientInitializationFailed,
- TallyTransportError::HttpStatus { status: 400 },
- ] {
- assert_eq!(
- classify_error(&anyhow::Error::new(error)),
- HealthOutcome::ApplicationRejected
- );
- }
- for error in [
- TallyTransportError::ConnectionFailed,
- TallyTransportError::RequestTimedOut,
- TallyTransportError::HttpStatus { status: 503 },
- ] {
- assert_eq!(
- classify_error(&anyhow::Error::new(error)),
- HealthOutcome::TransportFailure
- );
- }
- }
-
- #[test]
- fn endpoint_identity_aliases_only_localhost_to_ipv4_loopback() {
- let runtime = TallyRuntime::default();
- let localhost_identity = EndpointKey::from_config(&TallyConfig {
- host: "localhost".to_string(),
- port: 9000,
- })
- .expect("localhost identity");
- let ipv4_identity = EndpointKey::from_config(&TallyConfig {
- host: "127.0.0.1".to_string(),
- port: 9000,
- })
- .expect("IPv4 loopback identity");
- assert_eq!(localhost_identity, ipv4_identity);
- let first = runtime
- .session(TallyConfig {
- host: "localhost".to_string(),
- port: 9000,
- })
- .expect("localhost session");
- let second = runtime
- .session(TallyConfig {
- host: "127.0.0.1".to_string(),
- port: 9000,
- })
- .expect("IPv4 loopback session");
- let third = runtime
- .session(TallyConfig {
- host: "::1".to_string(),
- port: 9000,
- })
- .expect("IPv6 loopback session");
- let fourth = runtime
- .session(TallyConfig {
- host: "127.0.0.2".to_string(),
- port: 9000,
- })
- .expect("alternate IPv4 loopback session");
- assert!(Arc::ptr_eq(&first, &second));
- assert!(!Arc::ptr_eq(&first, &third));
- assert!(!Arc::ptr_eq(&first, &fourth));
- assert!(!Arc::ptr_eq(&third, &fourth));
- let snapshots = runtime.snapshots().expect("runtime snapshots");
- assert_eq!(snapshots.len(), 3);
- assert_eq!(snapshots[0].canonical_endpoint, "http://127.0.0.1:9000");
- assert_eq!(snapshots[1].canonical_endpoint, "http://127.0.0.2:9000");
- assert_eq!(snapshots[2].canonical_endpoint, "http://[::1]:9000");
- }
-
- #[test]
- fn reviewed_probe_cache_preserves_observation_time_and_is_single_use() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9000,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- let review_id = "review-current";
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: review_id.to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
-
- let mut reservation = runtime
- .reserve_cached_probe_fresh(&config, review_id, 300_000)
- .expect("reserve cache")
- .expect("fresh reviewed probe");
- assert_eq!(reservation.observed_at_unix_ms(), observed_at_unix_ms);
- assert_eq!(reservation.result().companies[0].name, "Synthetic Company");
- assert!(runtime
- .reserve_cached_probe_fresh(&config, review_id, 300_000)
- .expect("second reserve")
- .is_none());
- assert!(reservation.consume().expect("consume reservation"));
- assert!(!reservation
- .consume()
- .expect("consuming an already consumed lease is inert"));
- assert!(runtime
- .reserve_cached_probe_fresh(&config, review_id, 300_000)
- .expect("reserve consumed cache")
- .is_none());
- }
-
- #[test]
- fn stale_review_id_cannot_consume_or_reserve_a_newer_probe() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9002,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-b".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
-
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-a", 300_000)
- .expect("reject stale review")
- .is_none());
- let mut reservation = runtime
- .reserve_cached_probe_fresh(&config, "review-b", 300_000)
- .expect("reserve current review")
- .expect("current review exists");
- assert!(reservation.release().expect("release current review"));
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-b", 300_000)
- .expect("retry current review")
- .is_some());
- }
-
- #[test]
- fn reviewed_probe_cache_rejects_future_expired_and_invalid_freshness() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9001,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- for observed_at_unix_ms in [
- chrono::Utc::now().timestamp_millis() + 1_000,
- chrono::Utc::now().timestamp_millis() - 301_000,
- ] {
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-expiry".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-expiry", 300_000)
- .expect("reserve cache")
- .is_none());
- }
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-expiry", 0)
- .is_err());
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-expiry", 600_001)
- .is_err());
- }
-
- #[test]
- fn replacing_a_qualified_review_does_not_renew_its_freshness_origin() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9003,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let freshness_origin_unix_ms = chrono::Utc::now().timestamp_millis() - 299_000;
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-original".to_string(),
- observed_at_unix_ms: freshness_origin_unix_ms,
- freshness_origin_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- let mut reservation = runtime
- .reserve_cached_probe_fresh(&config, "review-original", 300_000)
- .expect("reserve original")
- .expect("original remains barely fresh");
- assert!(reservation
- .replace(
- "review-qualified".to_string(),
- chrono::Utc::now().timestamp_millis(),
- synthetic_probe_result(),
- )
- .expect("replace reservation"));
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-qualified", 298_000)
- .expect("check inherited freshness")
- .is_none());
- }
-
- #[test]
- fn ordinary_read_admission_and_review_reservation_are_mutually_exclusive() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9004,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-lease".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
-
- let read_lease = runtime
- .begin_ordinary_read(&config)
- .expect("admit ordinary read");
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-lease", 300_000)
- .is_err());
- drop(read_lease);
-
- let reservation = runtime
- .reserve_cached_probe_fresh(&config, "review-lease", 300_000)
- .expect("reserve after read")
- .expect("fresh review");
- assert!(runtime.begin_ordinary_read(&config).is_err());
- assert!(reservation.authorize(&runtime, &config).is_ok());
- assert!(reservation
- .authorize(
- &runtime,
- &TallyConfig {
- host: "127.0.0.2".to_string(),
- port: 9004,
- },
- )
- .is_err());
- }
-
- #[tokio::test]
- async fn qualification_rejects_a_reservation_from_another_runtime_before_dispatch() {
- let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind cross-runtime qualification server");
- let address = listener.local_addr().expect("qualification server address");
- let config = TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- };
- let owner_runtime = TallyRuntime::default();
- let executing_runtime = TallyRuntime::default();
- let session = owner_runtime
- .session(config.clone())
- .expect("owner runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-cross-runtime".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- drop(session);
- let reservation = owner_runtime
- .reserve_cached_probe_fresh(&config, "review-cross-runtime", 300_000)
- .expect("reserve owner review")
- .expect("fresh owner review");
-
- let error = executing_runtime
- .qualify_selected_ledgers(
- config,
- &reservation,
- &verified_identity("Synthetic Company", "synthetic-guid"),
- )
- .await
- .expect_err("another runtime must not borrow the reservation");
- assert!(error
- .to_string()
- .contains("reviewed setup operation ownership changed"));
- assert!(
- tokio::time::timeout(std::time::Duration::from_millis(50), listener.accept(),)
- .await
- .is_err()
- );
- }
-
- #[tokio::test]
- async fn production_identity_bracket_rechecks_the_tuple_around_selected_ledger_qualification() {
- let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind qualification server");
- let address = listener.local_addr().expect("qualification server address");
- let company_list = r#"00000000-0000-4000-8000-00000000000110000120260401"#;
- let ledger_export = Fixture::NormalExport.body().into_owned();
- let server = tokio::spawn(async move {
- let mut requests = Vec::new();
- for body in [
- company_list.to_string(),
- company_list.to_string(),
- ledger_export,
- company_list.to_string(),
- ] {
- let (mut socket, _) = listener.accept().await.expect("accept reserved read");
- let request = read_http_request(&mut socket).await;
- assert!(request.starts_with(b"POST /"));
- requests.push(request);
- socket
- .write_all(&utf16_xml_response(body))
- .await
- .expect("write reserved read response");
- }
- requests
- });
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-qualified-tuple".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- drop(session);
- let reservation = runtime
- .reserve_cached_probe_fresh(&config, "review-qualified-tuple", 300_000)
- .expect("reserve reviewed setup")
- .expect("fresh review");
-
- let ordinary_read = runtime
- .fetch_companies(config.clone())
- .await
- .expect_err("ordinary reads must remain blocked by the reservation");
- assert!(ordinary_read
- .to_string()
- .contains("reviewed setup operation is in progress"));
- let companies = runtime
- .fetch_companies_for_reservation(config.clone(), &reservation)
- .await
- .expect("reservation owner may recheck company tuple");
- assert_eq!(companies.len(), 1);
- let identity = VerifiedCompanyIdentity::from_observed_companies(
- "BRIDGE SYNTHETIC BOOK".to_string(),
- "00000000-0000-4000-8000-000000000001".to_string(),
- "100001".to_string(),
- "20260401".to_string(),
- &[TallyCompany {
- name: "BRIDGE SYNTHETIC BOOK".to_string(),
- guid: Some("00000000-0000-4000-8000-000000000001".to_string()),
- company_number: Some("100001".to_string()),
- books_from: Some("20260401".to_string()),
- }],
- )
- .expect("synthetic qualification identity is complete");
- let observation = runtime
- .qualify_selected_ledgers(config, &reservation, &identity)
- .await
- .expect("qualification must run after the reserved tuple recheck");
- assert_eq!(observation.result_bucket, "non_empty_observed");
- let requests = server.await.expect("finish reserved qualification server");
- assert_eq!(requests.len(), 4);
- let decoded = requests
- .iter()
- .map(|request| {
- let body_start = request
- .windows(4)
- .position(|bytes| bytes == b"\r\n\r\n")
- .expect("request has complete HTTP headers")
- + 4;
- bridge_tally_protocol::decode_tally_text_bytes_limited(
- &request[body_start..],
- request.len() - body_start,
- )
- .expect("decode dispatched Tally request")
- .text
- })
- .collect::>();
- for (index, request) in [0, 1, 3].into_iter().map(|index| (index, &decoded[index])) {
- assert!(
- request.contains("Collection"),
- "request {index}"
- );
- assert!(request.contains("Company"), "request {index}");
- assert!(!request.contains(""), "request {index}");
- }
- assert!(decoded[2].contains("BRIDGE SYNTHETIC BOOK"));
- }
-
- #[test]
- fn dropping_a_review_reservation_restores_the_same_fresh_review() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9005,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-drop".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- drop(
- runtime
- .reserve_cached_probe_fresh(&config, "review-drop", 300_000)
- .expect("reserve review")
- .expect("fresh review"),
- );
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-drop", 300_000)
- .expect("reserve after drop")
- .is_some());
- }
-
- #[tokio::test]
- async fn aborting_a_task_drops_and_releases_its_review_reservation() {
- let runtime = Arc::new(TallyRuntime::default());
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9006,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-abort".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- let (held_tx, held_rx) = tokio::sync::oneshot::channel();
- let task_runtime = Arc::clone(&runtime);
- let task_config = config.clone();
- let task = tokio::spawn(async move {
- let _reservation = task_runtime
- .reserve_cached_probe_fresh(&task_config, "review-abort", 300_000)
- .expect("reserve review")
- .expect("fresh review");
- held_tx.send(()).expect("announce held reservation");
- std::future::pending::<()>().await;
- });
- held_rx.await.expect("reservation was held");
- task.abort();
- let _ = task.await;
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-abort", 300_000)
- .expect("reserve after abort")
- .is_some());
- }
-
- #[tokio::test]
- async fn aborting_pending_qualification_releases_review_and_active_request() {
- let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
- .await
- .expect("bind pending qualification server");
- let address = listener.local_addr().expect("pending server address");
- let (accepted_tx, accepted_rx) = tokio::sync::oneshot::channel();
- let server = tokio::spawn(async move {
- let (_socket, _) = listener.accept().await.expect("accept qualification");
- accepted_tx.send(()).expect("announce accepted request");
- std::future::pending::<()>().await;
- });
- let runtime = Arc::new(TallyRuntime::default());
- let config = TallyConfig {
- host: address.ip().to_string(),
- port: address.port(),
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-pending".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- drop(session);
- let task_runtime = Arc::clone(&runtime);
- let task_config = config.clone();
- let task = tokio::spawn(async move {
- let reservation = task_runtime
- .reserve_cached_probe_fresh(&task_config, "review-pending", 300_000)
- .expect("reserve pending review")
- .expect("fresh pending review");
- let _ = task_runtime
- .qualify_selected_ledgers(
- task_config,
- &reservation,
- &verified_identity("Synthetic Company", "synthetic-guid"),
- )
- .await;
- });
- accepted_rx.await.expect("qualification reached server");
- task.abort();
- let _ = task.await;
- server.abort();
- let snapshots = runtime.snapshots().expect("runtime snapshots after abort");
- assert_eq!(snapshots.len(), 1);
- assert_eq!(snapshots[0].active_requests, 0);
- assert!(snapshots[0].active_request_ids.is_empty());
- assert!(runtime
- .reserve_cached_probe_fresh(&config, "review-pending", 300_000)
- .expect("reserve after pending abort")
- .is_some());
- }
-
- #[test]
- fn stale_guard_cannot_release_or_consume_a_newer_reserved_review() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9007,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-old".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- let mut stale = runtime
- .reserve_cached_probe_fresh(&config, "review-old", 300_000)
- .expect("reserve old")
- .expect("old review");
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-new".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: true,
- });
- assert!(!stale.consume().expect("stale consume is inert"));
- drop(stale);
- let cache = session.cached_probe.read().expect("capability cache");
- let current = cache.as_ref().expect("new review remains");
- assert_eq!(current.review_id, "review-new");
- assert!(current.reserved);
- }
-
- #[test]
- fn stale_guard_cannot_replace_a_newer_reserved_review() {
- let runtime = TallyRuntime::default();
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9008,
- };
- let session = runtime.session(config.clone()).expect("runtime session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-old".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- let mut stale = runtime
- .reserve_cached_probe_fresh(&config, "review-old", 300_000)
- .expect("reserve old")
- .expect("old review");
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-new".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: true,
- });
-
- assert!(!stale
- .replace(
- "review-illegal-replacement".to_string(),
- observed_at_unix_ms,
- synthetic_probe_result(),
- )
- .expect("stale replace is inert"));
- drop(stale);
- let cache = session.cached_probe.read().expect("capability cache");
- let current = cache.as_ref().expect("new review remains");
- assert_eq!(current.review_id, "review-new");
- assert!(current.reserved);
- }
-
- #[test]
- fn held_review_reservation_prevents_endpoint_session_eviction() {
- let runtime = TallyRuntime::default();
- let reserved_config = TallyConfig {
- host: "127.0.0.1".to_string(),
- port: 9200,
- };
- let reserved_endpoint = EndpointKey::from_config(&reserved_config).unwrap();
- let session = runtime
- .session(reserved_config.clone())
- .expect("reserved session");
- let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
- *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
- review_id: "review-capacity".to_string(),
- observed_at_unix_ms,
- freshness_origin_unix_ms: observed_at_unix_ms,
- result: synthetic_probe_result(),
- reserved: false,
- });
- drop(session);
- let _reservation = runtime
- .reserve_cached_probe_fresh(&reserved_config, "review-capacity", 300_000)
- .expect("reserve capacity review")
- .expect("fresh capacity review");
- for host_suffix in 2..=MAX_ENDPOINT_SESSIONS {
- runtime
- .session(TallyConfig {
- host: format!("127.0.0.{host_suffix}"),
- port: 9200,
- })
- .expect("fill endpoint capacity");
- }
- runtime
- .session(TallyConfig {
- host: "127.0.0.254".to_string(),
- port: 9200,
- })
- .expect("evict one unreserved session");
- assert!(runtime
- .sessions
- .lock()
- .expect("session registry")
- .contains_key(&reserved_endpoint));
- }
-
- #[tokio::test]
- async fn cancellation_registry_cancels_and_releases_requests() {
- let runtime = Arc::new(TallyRuntime::default());
- let config = TallyConfig {
- host: "localhost".to_string(),
- port: 9100,
- };
- let runtime_task = Arc::clone(&runtime);
- let task = tokio::spawn(async move {
- runtime_task
- .execute(
- config,
- ReadOperation::OtherRead,
- ReadRetryPolicy::SINGLE_ATTEMPT,
- |_client| async {
- tokio::time::sleep(std::time::Duration::from_secs(30)).await;
- Ok::<_, anyhow::Error>(())
- },
- )
- .await
- });
- tokio::task::yield_now().await;
- let snapshot = runtime
- .snapshots()
- .expect("runtime snapshots")
- .pop()
- .expect("active session");
- assert_eq!(snapshot.active_requests, 1);
- let session = runtime
- .sessions
- .lock()
- .expect("sessions lock")
- .values()
- .next()
- .expect("session")
- .session
- .clone();
- let request_id = session
- .active_requests
- .lock()
- .expect("request lock")
- .keys()
- .next()
- .expect("request ID")
- .clone();
- assert!(runtime.cancel_request(&request_id).expect("cancel request"));
- assert!(task.await.expect("request task").is_err());
- assert_eq!(
- runtime.snapshots().expect("runtime snapshots")[0].active_requests,
- 0
- );
- assert_eq!(
- runtime.snapshots().expect("runtime snapshots")[0].consecutive_failures,
- 0,
- "operator cancellation must not degrade endpoint health"
- );
- }
-
- #[test]
- fn telemetry_preview_is_privacy_reduced_and_checksummed() {
- let preview = TallyRuntime::default()
- .telemetry_preview()
- .expect("telemetry preview");
- assert_eq!(preview.schema, "bridge.tally.telemetry-preview/2");
- assert_eq!(preview.payload_sha256.len(), 64);
- let preview_value: serde_json::Value =
- serde_json::from_str(&preview.preview_json).expect("valid preview JSON");
- assert_eq!(
- preview_value["privacy_profile"],
- "fixed_dimensions_bucketed_values_v1"
- );
- assert_eq!(preview_value["authenticity_claim"], "none");
- }
-}
+#[path = "runtime_tests.rs"]
+mod tests;
diff --git a/src-tauri/src/tally/runtime_tests.rs b/src-tauri/src/tally/runtime_tests.rs
new file mode 100644
index 00000000..75657380
--- /dev/null
+++ b/src-tauri/src/tally/runtime_tests.rs
@@ -0,0 +1,1940 @@
+use super::*;
+use crate::commands::VerifiedCompanyIdentity;
+use crate::tally::TallyProduct;
+use anyhow::Context;
+use bridge_tally_core::CapabilityProfile;
+use std::collections::BTreeMap;
+use tally_protocol_simulator::Fixture;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+fn verified_identity(name: &str, guid: &str) -> VerifiedCompanyIdentity {
+ VerifiedCompanyIdentity::test_fixture(name, guid)
+}
+
+fn captured_aarav_company_list_and_identity() -> (String, VerifiedCompanyIdentity) {
+ let bytes = include_bytes!("../../crates/bridge-tally-protocol/tests/fixtures/agent/native-licensed-release-companies.utf16le.xml");
+ let xml = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
+ bytes,
+ "text/xml; charset=utf-16",
+ bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
+ bytes.len(),
+ )
+ .expect("captured Company collection decodes")
+ .text;
+ let rows = parse_companies_from_collection(&xml).unwrap();
+ let row = rows
+ .iter()
+ .find(|row| row.name == "Aarav Trading Company Demo")
+ .unwrap();
+ let identity = VerifiedCompanyIdentity::from_observed_companies(
+ row.name.clone(),
+ row.guid.clone().unwrap(),
+ row.company_number.clone().unwrap(),
+ row.books_from.clone().unwrap(),
+ &rows,
+ )
+ .unwrap();
+ (xml, identity)
+}
+
+#[test]
+fn agent_company_list_evidence_hashes_the_encoded_utf16_response_bytes() {
+ let response = "g-1120260401".to_string();
+ let encoded = bridge_tally_protocol::encode_tally_xml_request_utf16le(&response);
+ let expected_bytes = encoded.len();
+ let expected_sha = Sha256::digest(&encoded)
+ .iter()
+ .map(|byte| format!("{byte:02x}"))
+ .collect::();
+ let listed = agent_company_list_from_response(response, expected_bytes, expected_sha.clone())
+ .expect("company list");
+ assert_eq!(listed.response_bytes, expected_bytes);
+ assert_eq!(listed.response_sha256, expected_sha);
+ assert_eq!(listed.companies.len(), 1);
+}
+
+#[test]
+fn party_ledger_master_evidence_includes_currency_probe_and_source_responses() {
+ let source = |master_response_sha256: &str| PartyLedgerMasterSource {
+ company: "Synthetic Books".to_string(),
+ company_guid: "company-guid".to_string(),
+ currency_assertion: OutstandingsCurrencyAssertion::Inr,
+ currency_decimal_places: 2,
+ from: TallyDate::parse("20260401").expect("source from"),
+ to: TallyDate::parse("20260731").expect("source to"),
+ rows: Vec::new(),
+ request_sha256: "0".repeat(64),
+ master_response_sha256: master_response_sha256.to_string(),
+ balance_response_sha256: "b".repeat(64),
+ group_response_sha256: "c".repeat(64),
+ master_response_bytes: 11,
+ balance_response_bytes: 13,
+ group_response_bytes: 17,
+ groups: Vec::new(),
+ };
+ let currency = RuntimeReadEvidence::paired("", sha256_hex(b"currency-response"), 19);
+ let baseline = TallyRuntime::party_ledger_master_source_evidence(
+ &source(&"a".repeat(64)),
+ currency.clone(),
+ );
+ let changed = TallyRuntime::party_ledger_master_source_evidence(
+ &source(&"d".repeat(64)),
+ currency.clone(),
+ );
+ let changed_currency = TallyRuntime::party_ledger_master_source_evidence(
+ &source(&"a".repeat(64)),
+ RuntimeReadEvidence::paired("", sha256_hex(b"changed-currency-response"), 23),
+ );
+ let source_scope = "0".repeat(64);
+ let source_responses =
+ sha256_hex(format!("{}:{}:{}", "a".repeat(64), "b".repeat(64), "c".repeat(64)).as_bytes());
+ assert_eq!(
+ baseline.request_sha256,
+ sha256_hex(format!("{}:{source_scope}", currency.request_sha256).as_bytes())
+ );
+ assert_eq!(
+ baseline.response_sha256,
+ sha256_hex(format!("{}:{source_responses}", currency.response_sha256).as_bytes())
+ );
+ assert_eq!(baseline.request_sha256, changed_currency.request_sha256);
+ assert_ne!(baseline.response_sha256, changed_currency.response_sha256);
+ assert_eq!(changed_currency.bytes, (11 + 13 + 17 + 23) * 2);
+
+ assert_eq!(baseline.request_sha256, changed.request_sha256);
+ assert_ne!(baseline.response_sha256, changed.response_sha256);
+ assert_eq!(baseline.bytes, (11 + 13 + 17 + 19) * 2);
+}
+
+#[test]
+fn paired_evidence_commits_to_utf16le_request_body_not_utf8_source() {
+ let request = "₹ & Co";
+ let mut encoded = vec![0xff, 0xfe];
+ encoded.extend(request.encode_utf16().flat_map(u16::to_le_bytes));
+ let evidence = RuntimeReadEvidence::paired(request, "r".repeat(64), 7);
+ assert_eq!(evidence.request_sha256, sha256_hex(&encoded));
+ assert_ne!(evidence.request_sha256, sha256_hex(request.as_bytes()));
+ assert_eq!(evidence.bytes, 14);
+}
+
+#[test]
+fn outstandings_wire_evidence_changes_with_any_native_report_response() {
+ let receivable = RuntimeReadEvidence::paired("", sha256_hex(b"r1"), 3);
+ let payable = RuntimeReadEvidence::paired("", sha256_hex(b"p1"), 5);
+ let baseline = receivable.clone().combine(payable.clone());
+ let changed = receivable.combine(RuntimeReadEvidence::paired(
+ "",
+ sha256_hex(b"p2"),
+ 5,
+ ));
+
+ assert_eq!(baseline.bytes, 16, "both paired responses are counted");
+ assert_ne!(baseline.response_sha256, changed.response_sha256);
+ assert_ne!(baseline.request_sha256, String::new());
+}
+
+fn utf16_xml_response(body: impl AsRef) -> Vec {
+ let body = bridge_tally_protocol::encode_tally_xml_request_utf16le(body.as_ref());
+ utf16_xml_response_bytes(&body)
+}
+
+fn utf16_xml_response_bytes(body: &[u8]) -> Vec {
+ let headers = format!(
+ "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-16\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
+ body.len()
+ );
+ [headers.as_bytes(), body].concat()
+}
+
+fn utf8_status_response(body: impl AsRef) -> Vec {
+ let body = body.as_ref().as_bytes();
+ let headers = format!(
+ "HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
+ body.len()
+ );
+ [headers.as_bytes(), body].concat()
+}
+
+async fn read_http_request(socket: &mut tokio::net::TcpStream) -> Vec {
+ let mut request = Vec::new();
+ let mut buffer = [0_u8; 4096];
+ let (header_end, content_length) = loop {
+ let read = socket.read(&mut buffer).await.expect("read request");
+ assert!(read > 0, "request ended before headers completed");
+ request.extend_from_slice(&buffer[..read]);
+ let Some(header_end) = request.windows(4).position(|bytes| bytes == b"\r\n\r\n") else {
+ continue;
+ };
+ let header =
+ std::str::from_utf8(&request[..header_end]).expect("request headers are valid UTF-8");
+ let content_length = header
+ .lines()
+ .find_map(|line| {
+ let (name, value) = line.split_once(':')?;
+ name.eq_ignore_ascii_case("content-length")
+ .then_some(value.trim())
+ })
+ .map(|value| {
+ value
+ .parse::()
+ .expect("request Content-Length is numeric")
+ })
+ .unwrap_or(0);
+ break (header_end + 4, content_length);
+ };
+ while request.len() < header_end + content_length {
+ let read = socket.read(&mut buffer).await.expect("read request body");
+ assert!(read > 0, "request ended before declared body completed");
+ request.extend_from_slice(&buffer[..read]);
+ }
+ request
+}
+
+#[test]
+fn ageing_anchor_serializes_as_an_explicit_wire_contract() {
+ assert_eq!(
+ serde_json::to_value(OutstandingsAgeingAnchor::DueDate).unwrap(),
+ serde_json::json!("due_date")
+ );
+ assert_eq!(
+ serde_json::to_value(OutstandingsAgeingAnchor::BillDate).unwrap(),
+ serde_json::json!("bill_date")
+ );
+}
+
+#[test]
+fn outstandings_read_strategy_serializes_as_an_explicit_wire_contract() {
+ assert_eq!(
+ serde_json::to_value(OutstandingsReadStrategy::NativeBills).unwrap(),
+ serde_json::json!("native_bills")
+ );
+ assert_eq!(
+ serde_json::to_value(OutstandingsReadStrategy::VoucherScan).unwrap(),
+ serde_json::json!("voucher_scan")
+ );
+}
+
+#[test]
+fn single_company_forex_ledger_capture_returns_a_typed_partial() {
+ const FOREX_LEDGER_CAPTURE: &[u8] = include_bytes!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/ledgers_forex_composite_live.utf16le.xml"
+ );
+ let ledger_body = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
+ FOREX_LEDGER_CAPTURE,
+ "text/xml; charset=utf-16",
+ bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
+ FOREX_LEDGER_CAPTURE.len(),
+ )
+ .expect("captured forex ledger response decodes")
+ .text;
+ let admitted = admit_native_ledger_snapshot(
+ parse_native_ledger_snapshot(&ledger_body)
+ .map_err(anyhow::Error::from)
+ .context("stable native ledger snapshot"),
+ )
+ .expect("a foreign-currency ledger is an in-band partial");
+
+ assert!(matches!(
+ admitted,
+ NativeLedgerSnapshotAdmission::Partial(OutstandingsLoadResult::Partial { reason, .. })
+ if reason.reason_code == "company_foreign_currency_ledger_balance"
+ && reason.foreign_currency_ledger_name.as_deref() == Some("FX USD Debtor 02")
+ ));
+}
+
+#[tokio::test]
+async fn single_company_read_returns_the_forex_capture_partial() {
+ const EXTENT: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
+ );
+ const FOREX_LEDGER_CAPTURE: &[u8] = include_bytes!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/ledgers_forex_composite_live.utf16le.xml"
+ );
+ const RECEIVABLE: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_aarav.xml"
+ );
+ const PAYABLE: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_payable_aarav.xml"
+ );
+ const STATUS: &str = "TallyPrime Server is Running";
+ let (company_list, identity) = captured_aarav_company_list_and_identity();
+
+ let extent = EXTENT;
+
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic outstandings server");
+ let address = listener.local_addr().expect("synthetic server address");
+ let server = tokio::spawn(async move {
+ let mut source_post_index = 0;
+ let mut company_post_index = 0;
+ for index in 0..30 {
+ let (mut socket, _) =
+ tokio::time::timeout(std::time::Duration::from_secs(2), listener.accept())
+ .await
+ .expect("outstandings request timed out")
+ .expect("accept outstandings request");
+ let request = read_http_request(&mut socket).await;
+ let response = if request.starts_with(b"GET /status") {
+ utf8_status_response(STATUS)
+ } else {
+ assert!(request.starts_with(b"POST /"), "request {index}");
+ let body_start = request
+ .windows(4)
+ .position(|bytes| bytes == b"\r\n\r\n")
+ .expect("complete request headers")
+ + 4;
+ let xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &request[body_start..],
+ request.len() - body_start,
+ )
+ .expect("decode request")
+ .text;
+ if xml.contains("BridgeCompanyExtent")
+ && !xml.contains("")
+ {
+ company_post_index += 1;
+ utf16_xml_response(&company_list)
+ } else {
+ let response = match source_post_index {
+ 0 | 1 | 10 | 11 => utf16_xml_response(extent),
+ 2 | 3 => utf16_xml_response(RECEIVABLE),
+ 6 | 7 => utf16_xml_response(PAYABLE),
+ _ => utf16_xml_response_bytes(FOREX_LEDGER_CAPTURE),
+ };
+ source_post_index += 1;
+ response
+ }
+ };
+ socket.write_all(&response).await.expect("write response");
+ }
+ assert_eq!(company_post_index, 4);
+ assert_eq!(source_post_index, 12);
+ });
+
+ let result = TallyRuntime::default()
+ .fetch_outstandings(
+ TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ },
+ &identity,
+ TallyDate::parse("20260401").expect("captured book as-of"),
+ OutstandingsCurrencyAssertion::Inr,
+ OutstandingsAgeingAnchor::DueDate,
+ )
+ .await
+ .expect("foreign-currency capture returns an in-band partial");
+
+ assert!(matches!(
+ result,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "company_foreign_currency_ledger_balance"
+ && reason.foreign_currency_ledger_name.as_deref() == Some("FX USD Debtor 02")
+ ));
+ server.await.expect("synthetic outstandings server task");
+}
+
+#[test]
+fn validation_lab_future_bill_stays_unaged_in_open_bill_output() {
+ let receivable = parse_native_bill_rows(
+ include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_validation_lab.xml"
+ ),
+ &TallyDate::parse("20250401").expect("captured BooksFrom"),
+ &TallyDate::parse("20260817").expect("capture as-of"),
+ )
+ .expect("captured validation-book rows parse");
+ let rows = all_open_bill_rows(
+ &receivable,
+ &[],
+ OutstandingsAgeingAnchor::DueDate,
+ &TallyDate::parse("20260817").expect("capture as-of"),
+ );
+ let future = rows
+ .iter()
+ .find(|row| row.reference == "ALPHA-FUTURE")
+ .expect("captured future-due bill remains present");
+ assert_eq!(future.amount.as_str(), "22222.00");
+ assert_eq!(future.age_days, None);
+
+ let bill_date_rows = all_open_bill_rows(
+ &receivable,
+ &[],
+ OutstandingsAgeingAnchor::BillDate,
+ &TallyDate::parse("20260817").expect("capture as-of"),
+ );
+ let bill_date_future = bill_date_rows
+ .iter()
+ .find(|row| row.reference == "ALPHA-FUTURE")
+ .expect("captured future-due bill remains present for bill-date ageing");
+ assert!(
+ bill_date_future.age_days.is_some(),
+ "the selected bill-date basis must not reuse the future due date"
+ );
+}
+
+#[test]
+fn settled_native_bill_rows_do_not_reach_statement_or_export_sources() {
+ let books_from = TallyDate::parse("20250401").expect("synthetic BooksFrom");
+ let as_of = TallyDate::parse("20260817").expect("synthetic as-of");
+ let parsed = parse_native_bill_rows(
+ "1-Aug-26SETTLED\
+ Synthetic Customer0.00\
+ 1-Aug-2616",
+ &books_from,
+ &as_of,
+ )
+ .expect("synthetic settled row parses at the wire boundary");
+
+ assert_eq!(parsed.len(), 1, "the fixture proves the native row existed");
+ assert!(
+ all_open_bill_rows(&parsed, &[], OutstandingsAgeingAnchor::DueDate, &as_of).is_empty(),
+ "a zero closing balance is not an open bill"
+ );
+}
+
+#[test]
+fn captured_validation_lab_bytes_render_a_complete_working_paper_end_to_end() {
+ const COMPANY: &str = "Bridge Validation Lab";
+ const COMPANY_GUID: &str = "c6afd306-00e1-4f51-802a-babe44daddd3";
+ let books_from = TallyDate::parse("20250401").expect("captured BooksFrom");
+ let as_of = TallyDate::parse("20260801").expect("captured as-of date");
+ let receivable_xml = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_receivable_validation_lab.xml"
+ );
+ let payable_xml = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/bills_payable_validation_lab.xml"
+ );
+ let groups_xml = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/group_snapshot_validation_lab.xml"
+ );
+ let ledgers_xml = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/native/ledger_snapshot_validation_lab.xml"
+ );
+
+ let receivable = parse_native_bill_rows(receivable_xml, &books_from, &as_of)
+ .expect("captured receivable rows parse");
+ let payable = parse_native_bill_rows(payable_xml, &books_from, &as_of)
+ .expect("captured payable rows parse");
+ // This captured Group body predates the request's response-bound
+ // compute. Add only that collection context for the parser contract;
+ // source-byte accounting below remains the exact captured response.
+ let groups_with_response_company_guid = groups_xml.replace(
+ "",
+ "c6afd306-00e1-4f51-802a-babe44daddd3",
+ );
+ let groups = parse_native_group_snapshot(&groups_with_response_company_guid, COMPANY_GUID)
+ .expect("captured group identity and ancestry parse");
+ let ledgers =
+ parse_native_ledger_snapshot(ledgers_xml).expect("captured ledger controls parse");
+ let source_bytes = receivable_xml
+ .len()
+ .checked_add(payable_xml.len())
+ .and_then(|value| value.checked_add(groups_xml.len()))
+ .and_then(|value| value.checked_add(ledgers_xml.len()))
+ .expect("captured source byte count fits usize");
+ let computed = compute_native_outstandings(
+ COMPANY,
+ &receivable,
+ &payable,
+ NativeMasterSnapshot {
+ ledgers: &ledgers,
+ groups: NativeGroupSnapshot::Complete(&groups),
+ },
+ NativeAgeingAnchor::DueDate,
+ &as_of,
+ source_bytes,
+ )
+ .expect("captured native controls compute");
+ assert_eq!(
+ native_crosscheck_partial_reason(&computed, &as_of),
+ None,
+ "captured overdue counters must positively prove the requested date"
+ );
+
+ let statement_open_bills = all_open_bill_rows(
+ &receivable,
+ &payable,
+ OutstandingsAgeingAnchor::DueDate,
+ &as_of,
+ );
+ assert_eq!(statement_open_bills.len(), 6);
+ assert!(statement_open_bills.iter().all(|row| !row.amount.is_zero()));
+ let statement_unallocated_by_party = all_unallocated_parties(&computed.residuals);
+ let result = OutstandingsLoadResult::Complete {
+ report: Box::new(computed.report),
+ read_strategy: OutstandingsReadStrategy::NativeBills,
+ currency_assertion: OutstandingsCurrencyAssertion::Inr,
+ ageing_anchor: OutstandingsAgeingAnchor::DueDate,
+ synced_at_unix_ms: 1_777_000_000_000,
+ unallocated_total: Some(computed.residual_total),
+ statement_unallocated_by_party,
+ statement_open_bills,
+ };
+ let source = crate::reports::outstandings_working_paper_store::source_from_complete_result(
+ &result,
+ COMPANY_GUID,
+ )
+ .expect("captured source stays inside export budgets")
+ .expect("captured complete result substantiates a source");
+ assert_eq!(source.source_bytes, 33_575);
+ assert_eq!(source.open_bills.len(), 6);
+ let paper =
+ crate::reports::outstandings_working_paper::build_outstandings_working_paper(source)
+ .expect("captured exact controls reconcile into a working paper");
+ let workbook =
+ crate::reports::outstandings_working_paper_xlsx::render_outstandings_working_paper_xlsx(
+ &paper,
+ )
+ .expect("captured working paper renders");
+ assert!(workbook.len() > 200);
+ assert_eq!(&workbook[0..2], b"PK");
+}
+
+#[test]
+fn raw_overdue_disagreement_withholds_native_complete() {
+ let rows = parse_native_bill_rows(
+ "1-Jul-26MISMATCH\
+ Synthetic Customer-100.00\
+ 1-Jul-2631",
+ &TallyDate::parse("20250401").expect("synthetic BooksFrom"),
+ &TallyDate::parse("20260817").expect("synthetic as-of"),
+ )
+ .expect("raw bill parses");
+ let computed = compute_native_outstandings(
+ "Synthetic Company",
+ &rows,
+ &[],
+ NativeMasterSnapshot {
+ ledgers: &[],
+ groups: NativeGroupSnapshot::LegacyFixtureWithoutGroups,
+ },
+ NativeAgeingAnchor::DueDate,
+ &TallyDate::parse("20260817").expect("synthetic as-of"),
+ 0,
+ )
+ .expect("arithmetic still computes for diagnostic comparison");
+ assert_eq!(
+ computed.overdue_crosscheck,
+ NativeOverdueCrosscheck::Inconsistent
+ );
+ assert_eq!(
+ native_crosscheck_partial_reason(&computed, &TallyDate::parse("20260817").unwrap()),
+ Some(OutstandingsPartialReason::code(
+ "native_overdue_crosscheck_mismatch"
+ ))
+ );
+}
+
+#[test]
+fn zero_bill_rows_with_a_ledger_residual_withhold_native_complete() {
+ let groups = parse_native_group_snapshot(
+ r#"
+ 11111111-1111-1111-1111-111111111111-0000000111111111-1111-1111-1111-111111111111Primary
+ "#,
+ "11111111-1111-1111-1111-111111111111",
+ )
+ .expect("synthetic group snapshot parses");
+ let ledgers = parse_native_ledger_snapshot(
+ r#"
+ Sundry Debtors-100.000No
+ "#,
+ )
+ .expect("synthetic ledger snapshot parses");
+ let requested_as_of = TallyDate::parse("20260817").expect("synthetic as-of");
+ let computed = compute_native_outstandings(
+ "Synthetic Company",
+ &[],
+ &[],
+ NativeMasterSnapshot {
+ ledgers: &ledgers,
+ groups: NativeGroupSnapshot::Complete(&groups),
+ },
+ NativeAgeingAnchor::DueDate,
+ &requested_as_of,
+ 0,
+ )
+ .expect("the residual is a partial-result diagnostic, not a parser failure");
+
+ assert_eq!(
+ computed.overdue_crosscheck,
+ NativeOverdueCrosscheck::UnconfirmedAsOfWithoutBillReferences
+ );
+ assert_eq!(
+ native_crosscheck_partial_reason(&computed, &requested_as_of),
+ Some(OutstandingsPartialReason::code(
+ WarningCode::NativeOutstandingsAsOfUnconfirmedWithoutBillReferences.as_str(),
+ ))
+ );
+}
+
+#[test]
+fn raw_empty_bill_references_preserve_each_amount_and_get_explicit_label() {
+ let raw_bills = "\
+ 1-Jul-26Synthetic Customer\
+ -40.001-Jul-2630\
+ 2-Jul-26 \n\t Synthetic Customer\
+ -60.002-Jul-2629\
+ ";
+ let as_of = TallyDate::parse("20260731").expect("synthetic as-of");
+ let parsed = parse_native_bill_rows(
+ raw_bills,
+ &TallyDate::parse("20250401").expect("synthetic BooksFrom"),
+ &as_of,
+ )
+ .expect("paired empty BILLREF values remain parseable");
+ let rows = all_open_bill_rows(&parsed, &[], OutstandingsAgeingAnchor::DueDate, &as_of);
+
+ assert_eq!(rows.len(), 2, "empty identities must not collapse rows");
+ let total = rows
+ .iter()
+ .try_fold(ExactDecimal::zero(), |sum, row| {
+ sum.checked_add(&row.amount)
+ })
+ .expect("synthetic bill total remains exact");
+ assert_eq!(total.as_str(), "100", "neither amount may be lost");
+ assert_eq!(
+ rows.iter()
+ .map(|row| row.reference.as_str())
+ .collect::>(),
+ vec!["No reference reported", "No reference reported"],
+ "client-facing rows must disclose the missing identity",
+ );
+}
+
+#[test]
+fn party_master_currency_assertion_rejects_a_changed_company_extent() {
+ const EXTENT: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/unit_a_company_extent_live.xml"
+ );
+ let read_extent_xml = EXTENT.replacen(
+ r#"bb8ad19e-6aef-4239-a917-87fec0c6215e"#,
+ r#"bb8ad19e-6aef-4239-a917-87fec0c6215e1"#,
+ 1,
+ );
+ let changed_extent_xml = read_extent_xml.replace(
+ "20260401",
+ "20260402",
+ );
+ let read_extent = bridge_tally_protocol::outstandings_shared::parse_company_book_extent(
+ &read_extent_xml,
+ "Aarav Trading Company Demo",
+ "bb8ad19e-6aef-4239-a917-87fec0c6215e",
+ )
+ .expect("captured extent parses");
+ let changed_extent = bridge_tally_protocol::outstandings_shared::parse_company_book_extent(
+ &changed_extent_xml,
+ "Aarav Trading Company Demo",
+ "bb8ad19e-6aef-4239-a917-87fec0c6215e",
+ )
+ .expect("changed synthetic extent parses");
+ let assertion = CompanyCurrencyRead {
+ currency: CompanyCurrency {
+ symbol: "₹".to_string(),
+ mailing_name: "INR".to_string(),
+ currency_count: 1,
+ decimal_places: 2,
+ is_inr: true,
+ },
+ extent: read_extent.clone(),
+ evidence: RuntimeReadEvidence::empty(),
+ }
+ .bind_party_ledger_master_assertion(OutstandingsCurrencyAssertion::Inr);
+
+ assert_eq!(
+ assertion
+ .require_opening_extent(&read_extent)
+ .expect("the observed extent releases INR"),
+ PartyLedgerMasterCurrency {
+ assertion: OutstandingsCurrencyAssertion::Inr,
+ decimal_places: 2,
+ }
+ );
+ let error = assertion
+ .require_opening_extent(&changed_extent)
+ .expect_err("a changed extent must not release INR");
+ assert!(matches!(
+ error.downcast_ref::(),
+ Some(PairedReadValidationError::CurrencyToMasterExtent)
+ ));
+}
+
+#[tokio::test]
+async fn detect_base_currency_rejects_book_drift_after_the_currency_read() {
+ const EXTENT: &str = include_str!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/agent/native-company-book-extents-with-number.utf8.xml"
+ );
+ const CURRENCY: &[u8] = include_bytes!(
+ "../../crates/bridge-tally-protocol/tests/fixtures/currency_inr_modern_live.utf16le.xml"
+ );
+ const STATUS: &str = "TallyPrime Server is Running";
+ let (company_list, identity) = captured_aarav_company_list_and_identity();
+
+ let currency = bridge_tally_protocol::decode_tally_xml_response_bytes_limited(
+ CURRENCY,
+ "text/xml; charset=utf-16",
+ bridge_tally_protocol::ExpectedTallyTextEncoding::Utf16Le,
+ CURRENCY.len(),
+ )
+ .expect("captured currency response decodes")
+ .text;
+
+ let opening_extent = EXTENT;
+ let closing_extent = opening_extent.replace(
+ "20260401",
+ "20260402",
+ );
+ assert_ne!(
+ closing_extent, opening_extent,
+ "the drift mutation must apply"
+ );
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind synthetic currency server");
+ let address = listener.local_addr().expect("synthetic server address");
+ let server = tokio::spawn(async move {
+ let mut source_post_index = 0;
+ for index in 0..13 {
+ let (mut socket, _) =
+ tokio::time::timeout(std::time::Duration::from_secs(2), listener.accept())
+ .await
+ .expect("currency request timed out")
+ .expect("accept currency request");
+ let request = read_http_request(&mut socket).await;
+ let response = if request.starts_with(b"GET /status") {
+ utf8_status_response(STATUS)
+ } else {
+ assert!(request.starts_with(b"POST /"), "request {index}");
+ let body_start = request
+ .windows(4)
+ .position(|bytes| bytes == b"\r\n\r\n")
+ .expect("complete request headers")
+ + 4;
+ let xml = bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &request[body_start..],
+ request.len() - body_start,
+ )
+ .expect("decode request")
+ .text;
+ if xml.contains("BridgeCompanyExtent")
+ && !xml.contains("")
+ {
+ utf16_xml_response(&company_list)
+ } else {
+ let response = match source_post_index {
+ 0 | 1 => utf16_xml_response(opening_extent),
+ 2 | 3 => utf16_xml_response(¤cy),
+ _ => utf16_xml_response(&closing_extent),
+ };
+ source_post_index += 1;
+ response
+ }
+ };
+ socket.write_all(&response).await.expect("write response");
+ }
+ });
+
+ let runtime = TallyRuntime::default();
+ let result = runtime
+ .detect_base_currency(
+ TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ },
+ &identity,
+ )
+ .await;
+
+ let error = result.expect_err("closing extent drift must reject the currency");
+ assert!(
+ error.chain().any(|cause| matches!(
+ cause.downcast_ref::(),
+ Some(PairedReadValidationError::CurrencyExtent)
+ )),
+ "unexpected error: {error:#}"
+ );
+ server.await.expect("synthetic currency server task");
+}
+
+fn synthetic_probe_result() -> TallyProbeResult {
+ TallyProbeResult {
+ connection: ConnectionStatus {
+ reachable: true,
+ compatible: false,
+ server_text: "Synthetic status".to_string(),
+ product: TallyProduct::Unknown,
+ error: None,
+ },
+ companies: vec![TallyCompany {
+ name: "Synthetic Company".to_string(),
+ guid: Some("synthetic-guid".to_string()),
+ company_number: None,
+ books_from: None,
+ }],
+ profile: CapabilityProfile {
+ profile_version: 2,
+ product: "Unknown".to_string(),
+ release: None,
+ license_tier: None,
+ mode: None,
+ transports: BTreeMap::new(),
+ features: BTreeMap::new(),
+ packs: BTreeMap::new(),
+ },
+ selected_read_scope: None,
+ passport_snapshot_id: None,
+ }
+}
+
+#[test]
+fn future_due_bill_from_raw_native_bytes_reaches_the_statement_source() {
+ let books_from = TallyDate::parse("20260401").expect("synthetic book start");
+ let as_of = TallyDate::parse("20260731").expect("synthetic as-of date");
+ let bills_xml = "\
+ 1-Jul-26FUTURE-1Synthetic Party\
+ -100.001-Aug-260\
+ ";
+ let ledger_xml = "";
+ let receivable = parse_native_bill_rows(bills_xml, &books_from, &as_of)
+ .expect("a future due date in raw native bytes must parse");
+ let ledgers = parse_native_ledger_snapshot(ledger_xml)
+ .expect("the synthetic raw ledger response must parse");
+ let computed = compute_native_outstandings(
+ "Synthetic Company",
+ &receivable,
+ &[],
+ NativeMasterSnapshot {
+ ledgers: &ledgers,
+ groups: NativeGroupSnapshot::LegacyFixtureWithoutGroups,
+ },
+ NativeAgeingAnchor::DueDate,
+ &as_of,
+ bills_xml.len() + ledger_xml.len(),
+ )
+ .expect("a future-due bill must not abort the native computation");
+ assert_eq!(computed.report.receivable_total.as_str(), "100");
+ assert_eq!(computed.report.top_parties[0].oldest_bill_age_days, None);
+ assert_eq!(computed.report.ageing.days_0_30.as_str(), "100");
+ assert_eq!(computed.report.ageing.days_31_60, ExactDecimal::zero());
+ assert_eq!(computed.report.ageing.days_61_90, ExactDecimal::zero());
+ assert_eq!(computed.report.ageing.days_90_plus, ExactDecimal::zero());
+ assert_eq!(computed.report.open_receivable_bill_count, 1);
+
+ let statement_rows =
+ all_open_bill_rows(&receivable, &[], OutstandingsAgeingAnchor::DueDate, &as_of);
+ assert_eq!(
+ statement_rows.len(),
+ 1,
+ "a future-due bill must remain available to the statement source"
+ );
+ assert_eq!(statement_rows[0].amount.as_str(), "100.00");
+ assert_eq!(statement_rows[0].age_days, None);
+ let statement = crate::reports::party_statement::build_party_statement(
+ "Synthetic Company",
+ as_of.as_str(),
+ "Synthetic Party",
+ &statement_rows,
+ &[],
+ )
+ .expect("the future-due bill must build a party statement");
+ assert_eq!(statement.bills.len(), 1);
+ assert_eq!(statement.bills[0].reference, "FUTURE-1");
+ assert_eq!(statement.bills[0].age_days, None);
+ assert_eq!(statement.bills[0].bucket, None);
+ assert_eq!(statement.bill_total.as_str(), "100");
+
+ let destination = tempfile::tempdir().expect("synthetic destination");
+ let approvals =
+ crate::reports::bulk_party_statement::PartyStatementDestinationApprovals::default();
+ let approval_id = approvals
+ .issue(destination.path().to_path_buf())
+ .expect("approve synthetic destination");
+ let approved_destination = approvals
+ .consume(&approval_id, destination.path())
+ .expect("consume synthetic approval");
+ let bulk = crate::reports::bulk_party_statement::write_bulk_party_statements(
+ &approved_destination,
+ "Synthetic Company",
+ as_of.as_str(),
+ "xlsx",
+ &statement_rows,
+ &[],
+ |party_statement| {
+ crate::reports::party_statement_xlsx::render_party_statement_xlsx(party_statement)
+ .map_err(|error| error.to_string())
+ },
+ )
+ .expect("a bulk run must write the future-due party statement");
+ assert_eq!(bulk.written.len(), 1);
+ let workbook = std::fs::File::open(destination.path().join(&bulk.written[0].file_name))
+ .expect("the bulk statement file exists");
+ let mut archive = zip::ZipArchive::new(workbook).expect("bulk output is an XLSX archive");
+ let mut workbook_text = String::new();
+ for entry_name in ["xl/worksheets/sheet1.xml", "xl/sharedStrings.xml"] {
+ let mut entry = archive
+ .by_name(entry_name)
+ .expect("the XLSX statement entry exists");
+ std::io::Read::read_to_string(&mut entry, &mut workbook_text)
+ .expect("the workbook XML is readable");
+ }
+ assert!(workbook_text.contains("FUTURE-1"));
+ assert!(workbook_text.contains("Not due"));
+ assert!(workbook_text.contains("Unaged"));
+}
+
+#[test]
+fn education_snapshot_period_refusal_is_an_in_band_partial() {
+ let result = match admit_native_ledger_snapshot_period(
+ DateBoundaryProfile::EducationRestricted,
+ TallyDate::parse("20260401").expect("Education-valid book start"),
+ TallyDate::parse("20260415").expect("ordinary calendar as-of"),
+ ) {
+ NativeLedgerSnapshotPeriodAdmission::Period(_) => {
+ panic!("an Education-refused as-of must not construct a snapshot period")
+ }
+ NativeLedgerSnapshotPeriodAdmission::Partial(partial) => partial,
+ };
+ assert!(matches!(
+ result,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "as_of_has_no_valid_window_boundary"
+ ));
+}
+
+#[cfg(feature = "voucher-scan")]
+#[test]
+fn outstandings_read_failures_are_partial_and_deadlines_recommend_restart() {
+ assert_eq!(
+ outstandings_read_failure_reason(&anyhow::Error::new(TallyTransportError::RequestTimedOut)),
+ "tally_segment_deadline_restart_recommended"
+ );
+ assert_eq!(
+ outstandings_read_failure_reason(&anyhow::Error::new(TallyTransportError::RequestFailed)),
+ "segment_request_failed"
+ );
+ assert_eq!(
+ outstandings_read_failure_reason(&anyhow::Error::new(TallyTransportError::HttpStatus {
+ status: 500
+ })),
+ "segment_http_status_failure"
+ );
+ assert_eq!(
+ outstandings_read_failure_reason(&anyhow::anyhow!("connection refused")),
+ "segment_read_failed"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn outstandings_read_transport_failure_feeds_breaker_and_preserves_typed_partial() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9120,
+ };
+ let result = runtime
+ .execute(
+ config,
+ ReadOperation::VoucherExport,
+ ReadRetryPolicy::SINGLE_ATTEMPT,
+ |_client| async {
+ Err::(anyhow::Error::new(
+ OutstandingsReadTransportFailure {
+ reason_code: "tally_segment_deadline_restart_recommended",
+ source: anyhow::Error::new(TallyTransportError::RequestTimedOut),
+ },
+ ))
+ },
+ )
+ .await;
+ let caller_value = partial_after_outstandings_read_transport_failure(result)
+ .expect("outstandings read transport failures stay in-band for the caller");
+ assert!(matches!(
+ caller_value,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "tally_segment_deadline_restart_recommended"
+ ));
+ assert_eq!(
+ runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
+ 1,
+ "the breaker must observe the transport failure before UI mapping"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn verification_partial_stays_partial_without_feeding_breaker() {
+ let runtime = TallyRuntime::default();
+ let result = runtime
+ .execute(
+ TallyConfig {
+ host: "localhost".to_string(),
+ port: 9121,
+ },
+ ReadOperation::VoucherExport,
+ ReadRetryPolicy::SINGLE_ATTEMPT,
+ |_client| async { Ok::<_, anyhow::Error>(partial_result("paired_segment_mismatch")) },
+ )
+ .await;
+ let caller_value = partial_after_outstandings_read_transport_failure(result)
+ .expect("verification partial remains an in-band result");
+ assert!(matches!(
+ caller_value,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "paired_segment_mismatch"
+ ));
+ assert_eq!(
+ runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
+ 0,
+ "a verification failure reached a responder and must not poison endpoint health"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[test]
+fn closing_coverage_drift_is_not_reported_as_uncovered_opening_bills() {
+ assert_eq!(
+ closing_coverage_partial_reason(false, true),
+ Some("ledger_master_identity_changed_during_scan")
+ );
+ assert_eq!(
+ closing_coverage_partial_reason(true, false),
+ Some("ledger_opening_bills_not_covered")
+ );
+ assert_eq!(closing_coverage_partial_reason(true, true), None);
+}
+
+#[cfg(feature = "voucher-scan")]
+#[test]
+fn intra_pair_ledger_coverage_drift_is_an_in_band_partial() {
+ assert_eq!(
+ paired_coverage_partial_reason(&LedgerOpeningCoverageRead::Drifted),
+ Some("ledger_master_identity_changed_during_scan")
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn witness_transport_failure_feeds_breaker_and_preserves_typed_partial() {
+ let error = fetch_empty_partition_witness(
+ VoucherAlterIdHighWater::parse("1").expect("positive high-water"),
+ || async { Err(anyhow::Error::new(TallyTransportError::RequestTimedOut)) },
+ )
+ .await
+ .expect_err("a witness transport failure must cross the runtime health boundary");
+ assert!(error.downcast_ref::().is_some());
+
+ let runtime = TallyRuntime::default();
+ let result = runtime
+ .execute(
+ TallyConfig {
+ host: "localhost".to_string(),
+ port: 9122,
+ },
+ ReadOperation::VoucherExport,
+ ReadRetryPolicy::SINGLE_ATTEMPT,
+ |_client| async {
+ Err::(outstandings_read_transport_failure(
+ anyhow::Error::new(TallyTransportError::RequestTimedOut),
+ ))
+ },
+ )
+ .await;
+ let caller_value = partial_after_outstandings_read_transport_failure(result)
+ .expect("witness transport failure stays in-band only after health accounting");
+
+ assert!(matches!(
+ caller_value,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "tally_segment_deadline_restart_recommended"
+ ));
+ assert_eq!(
+ runtime.snapshots().expect("runtime snapshot")[0].consecutive_failures,
+ 1,
+ "the breaker must observe the witness transport failure"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[tokio::test]
+async fn witness_non_transport_failure_remains_an_in_band_partial() {
+ let partial = fetch_empty_partition_witness(
+ VoucherAlterIdHighWater::parse("1").expect("positive high-water"),
+ || async { Err(anyhow::anyhow!("witness response was malformed")) },
+ )
+ .await
+ .expect("non-transport witness failure stays in-band")
+ .expect_err("a malformed witness cannot complete a non-empty book");
+
+ assert_eq!(
+ partial.reason_code,
+ "empty_date_witness_profile_unavailable"
+ );
+}
+
+#[cfg(feature = "voucher-scan")]
+#[test]
+fn outstandings_date_boundaries_follow_detected_mode_and_fallback_to_i12() {
+ let mut profile = synthetic_probe_result().profile;
+ profile.product = "TallyPrime Edit Log".to_string();
+ profile.mode = Some("Education".to_string());
+ assert_eq!(
+ select_date_boundary_profile(Some(&profile)),
+ DateBoundaryProfile::EducationRestricted
+ );
+
+ profile.mode = Some("Licensed".to_string());
+ assert_eq!(
+ select_date_boundary_profile(Some(&profile)),
+ DateBoundaryProfile::ModeAgnostic
+ );
+ assert_eq!(
+ select_date_boundary_profile(None),
+ DateBoundaryProfile::ModeAgnostic
+ );
+
+ profile.product = "Unknown".to_string();
+ profile.mode = Some("Education".to_string());
+ assert_eq!(
+ select_date_boundary_profile(Some(&profile)),
+ DateBoundaryProfile::ModeAgnostic,
+ "inconsistent or incomplete detection must rely on I12 rather than inventing compatibility evidence"
+ );
+}
+
+/// An uncalibrated segment width no longer refuses the read -- it selects
+/// the native bills path, which needs no width. What must NOT change is
+/// that a non-loopback endpoint is still refused.
+///
+/// This test previously asserted `outstandings_segment_sizing_uncalibrated`
+/// and, by using a non-loopback host, proved the refusal happened BEFORE
+/// endpoint admission. That refusal was the defect: production has no
+/// calibrated width by construction, so every shipped build returned it for
+/// every company on every book and the screen could only ever say "No Tally
+/// data was read". The reason code is gone with it.
+///
+/// The loopback guard is unchanged and still fails closed; it is simply now
+/// the first guard the native path reaches. That is the property worth
+/// pinning, so this asserts it directly rather than inferring it from an
+/// ordering that no longer exists.
+#[tokio::test]
+async fn uncalibrated_outstandings_takes_the_native_path_and_still_refuses_a_non_loopback_endpoint()
+{
+ let runtime = TallyRuntime::default();
+ #[cfg(feature = "voucher-scan")]
+ assert!(
+ runtime.outstandings_segment_policy.is_none(),
+ "a default runtime must have no calibrated width -- that is what routes to the native path"
+ );
+
+ let error = runtime
+ .fetch_outstandings(
+ TallyConfig {
+ host: "not-a-loopback-endpoint".to_string(),
+ port: 9000,
+ },
+ &verified_identity("Synthetic Company", "synthetic-guid"),
+ TallyDate::parse("20260731").unwrap(),
+ OutstandingsCurrencyAssertion::Inr,
+ OutstandingsAgeingAnchor::DueDate,
+ )
+ .await
+ .expect_err("a non-loopback endpoint must never be contacted");
+ assert!(
+ error.to_string().contains("non_loopback_forbidden"),
+ "loopback-only admission must still fail closed on the native path, got: {error}"
+ );
+}
+
+#[cfg(feature = "live-calibration-harness")]
+#[tokio::test]
+async fn calibrated_voucher_scan_withholds_totals_before_endpoint_admission_without_residual_coverage(
+) {
+ let result = TallyRuntime::for_billwise_lab_reconciliation_exit_check()
+ .fetch_outstandings(
+ TallyConfig {
+ host: "not-a-loopback-endpoint".to_string(),
+ port: 9000,
+ },
+ &verified_identity("Synthetic Company", "synthetic-guid"),
+ TallyDate::parse("20260731").unwrap(),
+ OutstandingsCurrencyAssertion::Inr,
+ OutstandingsAgeingAnchor::DueDate,
+ )
+ .await
+ .expect("missing coverage is an in-band partial result");
+ assert!(matches!(
+ result,
+ OutstandingsLoadResult::Partial { reason, .. }
+ if reason.reason_code == "unallocated_direct_postings_not_covered"
+ ));
+}
+
+#[cfg(all(feature = "voucher-scan", not(feature = "live-calibration-harness")))]
+#[test]
+fn default_build_has_no_outstandings_width_admission() {
+ let runtime = TallyRuntime::default();
+ assert!(runtime.outstandings_segment_policy.is_none());
+ assert!(runtime.outstandings_boundary_profile_override.is_none());
+}
+
+#[cfg(feature = "live-calibration-harness")]
+#[test]
+fn billwise_lab_exit_harness_uses_only_education_valid_boundaries() {
+ let runtime = TallyRuntime::for_billwise_lab_reconciliation_exit_check();
+ assert_eq!(
+ runtime.outstandings_boundary_profile_override,
+ Some(DateBoundaryProfile::EducationRestricted)
+ );
+ let reporting_window = DateWindow::parse(
+ runtime
+ .outstandings_boundary_profile_override
+ .expect("exit profile is fixed"),
+ "20240401",
+ "20260702",
+ )
+ .expect("accepted corpus extent uses Education-valid boundaries");
+ let partitions = reporting_window
+ .narrow_partitions()
+ .expect("Education-valid corpus partitions without day-3 synthesis");
+ assert!(partitions.iter().all(|partition| {
+ matches!(&partition.from().as_str()[6..8], "01" | "02" | "31")
+ && matches!(&partition.to().as_str()[6..8], "01" | "02" | "31")
+ }));
+
+ let unprofiled = DateWindow::parse(DateBoundaryProfile::ModeAgnostic, "20240401", "20260702")
+ .unwrap()
+ .narrow_partitions()
+ .unwrap();
+ assert!(unprofiled.iter().any(|partition| {
+ !matches!(&partition.from().as_str()[6..8], "01" | "02" | "31")
+ || !matches!(&partition.to().as_str()[6..8], "01" | "02" | "31")
+ }));
+}
+
+#[test]
+fn local_rejections_and_client_http_statuses_do_not_poison_endpoint_health() {
+ assert_eq!(
+ classify_failure(&anyhow::Error::new(
+ TallyRuntimeReadError::ApplicationResponseRejected,
+ )),
+ ReadFailureClass::Application
+ );
+ for error in [
+ TallyTransportError::RequestTooLarge { limit: 1024 },
+ TallyTransportError::PolicyInvalid { code: "test" },
+ TallyTransportError::EndpointInvalid { code: "test" },
+ TallyTransportError::ClientInitializationFailed,
+ TallyTransportError::HttpStatus { status: 400 },
+ ] {
+ assert_eq!(
+ classify_error(&anyhow::Error::new(error)),
+ HealthOutcome::ApplicationRejected
+ );
+ }
+ for error in [
+ TallyTransportError::ConnectionFailed,
+ TallyTransportError::RequestTimedOut,
+ TallyTransportError::HttpStatus { status: 503 },
+ ] {
+ assert_eq!(
+ classify_error(&anyhow::Error::new(error)),
+ HealthOutcome::TransportFailure
+ );
+ }
+}
+
+#[test]
+fn endpoint_identity_aliases_only_localhost_to_ipv4_loopback() {
+ let runtime = TallyRuntime::default();
+ let localhost_identity = EndpointKey::from_config(&TallyConfig {
+ host: "localhost".to_string(),
+ port: 9000,
+ })
+ .expect("localhost identity");
+ let ipv4_identity = EndpointKey::from_config(&TallyConfig {
+ host: "127.0.0.1".to_string(),
+ port: 9000,
+ })
+ .expect("IPv4 loopback identity");
+ assert_eq!(localhost_identity, ipv4_identity);
+ let first = runtime
+ .session(TallyConfig {
+ host: "localhost".to_string(),
+ port: 9000,
+ })
+ .expect("localhost session");
+ let second = runtime
+ .session(TallyConfig {
+ host: "127.0.0.1".to_string(),
+ port: 9000,
+ })
+ .expect("IPv4 loopback session");
+ let third = runtime
+ .session(TallyConfig {
+ host: "::1".to_string(),
+ port: 9000,
+ })
+ .expect("IPv6 loopback session");
+ let fourth = runtime
+ .session(TallyConfig {
+ host: "127.0.0.2".to_string(),
+ port: 9000,
+ })
+ .expect("alternate IPv4 loopback session");
+ assert!(Arc::ptr_eq(&first, &second));
+ assert!(!Arc::ptr_eq(&first, &third));
+ assert!(!Arc::ptr_eq(&first, &fourth));
+ assert!(!Arc::ptr_eq(&third, &fourth));
+ let snapshots = runtime.snapshots().expect("runtime snapshots");
+ assert_eq!(snapshots.len(), 3);
+ assert_eq!(snapshots[0].canonical_endpoint, "http://127.0.0.1:9000");
+ assert_eq!(snapshots[1].canonical_endpoint, "http://127.0.0.2:9000");
+ assert_eq!(snapshots[2].canonical_endpoint, "http://[::1]:9000");
+}
+
+#[test]
+fn reviewed_probe_cache_preserves_observation_time_and_is_single_use() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9000,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ let review_id = "review-current";
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: review_id.to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+
+ let mut reservation = runtime
+ .reserve_cached_probe_fresh(&config, review_id, 300_000)
+ .expect("reserve cache")
+ .expect("fresh reviewed probe");
+ assert_eq!(reservation.observed_at_unix_ms(), observed_at_unix_ms);
+ assert_eq!(reservation.result().companies[0].name, "Synthetic Company");
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, review_id, 300_000)
+ .expect("second reserve")
+ .is_none());
+ assert!(reservation.consume().expect("consume reservation"));
+ assert!(!reservation
+ .consume()
+ .expect("consuming an already consumed lease is inert"));
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, review_id, 300_000)
+ .expect("reserve consumed cache")
+ .is_none());
+}
+
+#[test]
+fn stale_review_id_cannot_consume_or_reserve_a_newer_probe() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9002,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-b".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-a", 300_000)
+ .expect("reject stale review")
+ .is_none());
+ let mut reservation = runtime
+ .reserve_cached_probe_fresh(&config, "review-b", 300_000)
+ .expect("reserve current review")
+ .expect("current review exists");
+ assert!(reservation.release().expect("release current review"));
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-b", 300_000)
+ .expect("retry current review")
+ .is_some());
+}
+
+#[test]
+fn reviewed_probe_cache_rejects_future_expired_and_invalid_freshness() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9001,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ for observed_at_unix_ms in [
+ chrono::Utc::now().timestamp_millis() + 1_000,
+ chrono::Utc::now().timestamp_millis() - 301_000,
+ ] {
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-expiry".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-expiry", 300_000)
+ .expect("reserve cache")
+ .is_none());
+ }
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-expiry", 0)
+ .is_err());
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-expiry", 600_001)
+ .is_err());
+}
+
+#[test]
+fn replacing_a_qualified_review_does_not_renew_its_freshness_origin() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9003,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let freshness_origin_unix_ms = chrono::Utc::now().timestamp_millis() - 299_000;
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-original".to_string(),
+ observed_at_unix_ms: freshness_origin_unix_ms,
+ freshness_origin_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ let mut reservation = runtime
+ .reserve_cached_probe_fresh(&config, "review-original", 300_000)
+ .expect("reserve original")
+ .expect("original remains barely fresh");
+ assert!(reservation
+ .replace(
+ "review-qualified".to_string(),
+ chrono::Utc::now().timestamp_millis(),
+ synthetic_probe_result(),
+ )
+ .expect("replace reservation"));
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-qualified", 298_000)
+ .expect("check inherited freshness")
+ .is_none());
+}
+
+#[test]
+fn ordinary_read_admission_and_review_reservation_are_mutually_exclusive() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9004,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-lease".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+
+ let read_lease = runtime
+ .begin_ordinary_read(&config)
+ .expect("admit ordinary read");
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-lease", 300_000)
+ .is_err());
+ drop(read_lease);
+
+ let reservation = runtime
+ .reserve_cached_probe_fresh(&config, "review-lease", 300_000)
+ .expect("reserve after read")
+ .expect("fresh review");
+ assert!(runtime.begin_ordinary_read(&config).is_err());
+ assert!(reservation.authorize(&runtime, &config).is_ok());
+ assert!(reservation
+ .authorize(
+ &runtime,
+ &TallyConfig {
+ host: "127.0.0.2".to_string(),
+ port: 9004,
+ },
+ )
+ .is_err());
+}
+
+#[tokio::test]
+async fn qualification_rejects_a_reservation_from_another_runtime_before_dispatch() {
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind cross-runtime qualification server");
+ let address = listener.local_addr().expect("qualification server address");
+ let config = TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ };
+ let owner_runtime = TallyRuntime::default();
+ let executing_runtime = TallyRuntime::default();
+ let session = owner_runtime
+ .session(config.clone())
+ .expect("owner runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-cross-runtime".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ drop(session);
+ let reservation = owner_runtime
+ .reserve_cached_probe_fresh(&config, "review-cross-runtime", 300_000)
+ .expect("reserve owner review")
+ .expect("fresh owner review");
+
+ let error = executing_runtime
+ .qualify_selected_ledgers(
+ config,
+ &reservation,
+ &verified_identity("Synthetic Company", "synthetic-guid"),
+ )
+ .await
+ .expect_err("another runtime must not borrow the reservation");
+ assert!(error
+ .to_string()
+ .contains("reviewed setup operation ownership changed"));
+ assert!(
+ tokio::time::timeout(std::time::Duration::from_millis(50), listener.accept(),)
+ .await
+ .is_err()
+ );
+}
+
+#[tokio::test]
+async fn production_identity_bracket_rechecks_the_tuple_around_selected_ledger_qualification() {
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind qualification server");
+ let address = listener.local_addr().expect("qualification server address");
+ let company_list = r#"00000000-0000-4000-8000-00000000000110000120260401"#;
+ let ledger_export = Fixture::NormalExport.body().into_owned();
+ let server = tokio::spawn(async move {
+ let mut requests = Vec::new();
+ for body in [
+ company_list.to_string(),
+ company_list.to_string(),
+ ledger_export,
+ company_list.to_string(),
+ ] {
+ let (mut socket, _) = listener.accept().await.expect("accept reserved read");
+ let request = read_http_request(&mut socket).await;
+ assert!(request.starts_with(b"POST /"));
+ requests.push(request);
+ socket
+ .write_all(&utf16_xml_response(body))
+ .await
+ .expect("write reserved read response");
+ }
+ requests
+ });
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-qualified-tuple".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ drop(session);
+ let reservation = runtime
+ .reserve_cached_probe_fresh(&config, "review-qualified-tuple", 300_000)
+ .expect("reserve reviewed setup")
+ .expect("fresh review");
+
+ let ordinary_read = runtime
+ .fetch_companies(config.clone())
+ .await
+ .expect_err("ordinary reads must remain blocked by the reservation");
+ assert!(ordinary_read
+ .to_string()
+ .contains("reviewed setup operation is in progress"));
+ let companies = runtime
+ .fetch_companies_for_reservation(config.clone(), &reservation)
+ .await
+ .expect("reservation owner may recheck company tuple");
+ assert_eq!(companies.len(), 1);
+ let identity = VerifiedCompanyIdentity::from_observed_companies(
+ "BRIDGE SYNTHETIC BOOK".to_string(),
+ "00000000-0000-4000-8000-000000000001".to_string(),
+ "100001".to_string(),
+ "20260401".to_string(),
+ &[TallyCompany {
+ name: "BRIDGE SYNTHETIC BOOK".to_string(),
+ guid: Some("00000000-0000-4000-8000-000000000001".to_string()),
+ company_number: Some("100001".to_string()),
+ books_from: Some("20260401".to_string()),
+ }],
+ )
+ .expect("synthetic qualification identity is complete");
+ let observation = runtime
+ .qualify_selected_ledgers(config, &reservation, &identity)
+ .await
+ .expect("qualification must run after the reserved tuple recheck");
+ assert_eq!(observation.result_bucket, "non_empty_observed");
+ let requests = server.await.expect("finish reserved qualification server");
+ assert_eq!(requests.len(), 4);
+ let decoded = requests
+ .iter()
+ .map(|request| {
+ let body_start = request
+ .windows(4)
+ .position(|bytes| bytes == b"\r\n\r\n")
+ .expect("request has complete HTTP headers")
+ + 4;
+ bridge_tally_protocol::decode_tally_text_bytes_limited(
+ &request[body_start..],
+ request.len() - body_start,
+ )
+ .expect("decode dispatched Tally request")
+ .text
+ })
+ .collect::>();
+ for (index, request) in [0, 1, 3].into_iter().map(|index| (index, &decoded[index])) {
+ assert!(
+ request.contains("Collection"),
+ "request {index}"
+ );
+ assert!(request.contains("Company"), "request {index}");
+ assert!(!request.contains(""), "request {index}");
+ }
+ assert!(decoded[2].contains("BRIDGE SYNTHETIC BOOK"));
+}
+
+#[test]
+fn dropping_a_review_reservation_restores_the_same_fresh_review() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9005,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-drop".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ drop(
+ runtime
+ .reserve_cached_probe_fresh(&config, "review-drop", 300_000)
+ .expect("reserve review")
+ .expect("fresh review"),
+ );
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-drop", 300_000)
+ .expect("reserve after drop")
+ .is_some());
+}
+
+#[tokio::test]
+async fn aborting_a_task_drops_and_releases_its_review_reservation() {
+ let runtime = Arc::new(TallyRuntime::default());
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9006,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-abort".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ let (held_tx, held_rx) = tokio::sync::oneshot::channel();
+ let task_runtime = Arc::clone(&runtime);
+ let task_config = config.clone();
+ let task = tokio::spawn(async move {
+ let _reservation = task_runtime
+ .reserve_cached_probe_fresh(&task_config, "review-abort", 300_000)
+ .expect("reserve review")
+ .expect("fresh review");
+ held_tx.send(()).expect("announce held reservation");
+ std::future::pending::<()>().await;
+ });
+ held_rx.await.expect("reservation was held");
+ task.abort();
+ let _ = task.await;
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-abort", 300_000)
+ .expect("reserve after abort")
+ .is_some());
+}
+
+#[tokio::test]
+async fn aborting_pending_qualification_releases_review_and_active_request() {
+ let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
+ .await
+ .expect("bind pending qualification server");
+ let address = listener.local_addr().expect("pending server address");
+ let (accepted_tx, accepted_rx) = tokio::sync::oneshot::channel();
+ let server = tokio::spawn(async move {
+ let (_socket, _) = listener.accept().await.expect("accept qualification");
+ accepted_tx.send(()).expect("announce accepted request");
+ std::future::pending::<()>().await;
+ });
+ let runtime = Arc::new(TallyRuntime::default());
+ let config = TallyConfig {
+ host: address.ip().to_string(),
+ port: address.port(),
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-pending".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ drop(session);
+ let task_runtime = Arc::clone(&runtime);
+ let task_config = config.clone();
+ let task = tokio::spawn(async move {
+ let reservation = task_runtime
+ .reserve_cached_probe_fresh(&task_config, "review-pending", 300_000)
+ .expect("reserve pending review")
+ .expect("fresh pending review");
+ let _ = task_runtime
+ .qualify_selected_ledgers(
+ task_config,
+ &reservation,
+ &verified_identity("Synthetic Company", "synthetic-guid"),
+ )
+ .await;
+ });
+ accepted_rx.await.expect("qualification reached server");
+ task.abort();
+ let _ = task.await;
+ server.abort();
+ let snapshots = runtime.snapshots().expect("runtime snapshots after abort");
+ assert_eq!(snapshots.len(), 1);
+ assert_eq!(snapshots[0].active_requests, 0);
+ assert!(snapshots[0].active_request_ids.is_empty());
+ assert!(runtime
+ .reserve_cached_probe_fresh(&config, "review-pending", 300_000)
+ .expect("reserve after pending abort")
+ .is_some());
+}
+
+#[test]
+fn stale_guard_cannot_release_or_consume_a_newer_reserved_review() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9007,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-old".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ let mut stale = runtime
+ .reserve_cached_probe_fresh(&config, "review-old", 300_000)
+ .expect("reserve old")
+ .expect("old review");
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-new".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: true,
+ });
+ assert!(!stale.consume().expect("stale consume is inert"));
+ drop(stale);
+ let cache = session.cached_probe.read().expect("capability cache");
+ let current = cache.as_ref().expect("new review remains");
+ assert_eq!(current.review_id, "review-new");
+ assert!(current.reserved);
+}
+
+#[test]
+fn stale_guard_cannot_replace_a_newer_reserved_review() {
+ let runtime = TallyRuntime::default();
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9008,
+ };
+ let session = runtime.session(config.clone()).expect("runtime session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-old".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ let mut stale = runtime
+ .reserve_cached_probe_fresh(&config, "review-old", 300_000)
+ .expect("reserve old")
+ .expect("old review");
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-new".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: true,
+ });
+
+ assert!(!stale
+ .replace(
+ "review-illegal-replacement".to_string(),
+ observed_at_unix_ms,
+ synthetic_probe_result(),
+ )
+ .expect("stale replace is inert"));
+ drop(stale);
+ let cache = session.cached_probe.read().expect("capability cache");
+ let current = cache.as_ref().expect("new review remains");
+ assert_eq!(current.review_id, "review-new");
+ assert!(current.reserved);
+}
+
+#[test]
+fn held_review_reservation_prevents_endpoint_session_eviction() {
+ let runtime = TallyRuntime::default();
+ let reserved_config = TallyConfig {
+ host: "127.0.0.1".to_string(),
+ port: 9200,
+ };
+ let reserved_endpoint = EndpointKey::from_config(&reserved_config).unwrap();
+ let session = runtime
+ .session(reserved_config.clone())
+ .expect("reserved session");
+ let observed_at_unix_ms = chrono::Utc::now().timestamp_millis();
+ *session.cached_probe.write().expect("capability cache") = Some(CachedProbe {
+ review_id: "review-capacity".to_string(),
+ observed_at_unix_ms,
+ freshness_origin_unix_ms: observed_at_unix_ms,
+ result: synthetic_probe_result(),
+ reserved: false,
+ });
+ drop(session);
+ let _reservation = runtime
+ .reserve_cached_probe_fresh(&reserved_config, "review-capacity", 300_000)
+ .expect("reserve capacity review")
+ .expect("fresh capacity review");
+ for host_suffix in 2..=MAX_ENDPOINT_SESSIONS {
+ runtime
+ .session(TallyConfig {
+ host: format!("127.0.0.{host_suffix}"),
+ port: 9200,
+ })
+ .expect("fill endpoint capacity");
+ }
+ runtime
+ .session(TallyConfig {
+ host: "127.0.0.254".to_string(),
+ port: 9200,
+ })
+ .expect("evict one unreserved session");
+ assert!(runtime
+ .sessions
+ .lock()
+ .expect("session registry")
+ .contains_key(&reserved_endpoint));
+}
+
+#[tokio::test]
+async fn cancellation_registry_cancels_and_releases_requests() {
+ let runtime = Arc::new(TallyRuntime::default());
+ let config = TallyConfig {
+ host: "localhost".to_string(),
+ port: 9100,
+ };
+ let runtime_task = Arc::clone(&runtime);
+ let task = tokio::spawn(async move {
+ runtime_task
+ .execute(
+ config,
+ ReadOperation::OtherRead,
+ ReadRetryPolicy::SINGLE_ATTEMPT,
+ |_client| async {
+ tokio::time::sleep(std::time::Duration::from_secs(30)).await;
+ Ok::<_, anyhow::Error>(())
+ },
+ )
+ .await
+ });
+ tokio::task::yield_now().await;
+ let snapshot = runtime
+ .snapshots()
+ .expect("runtime snapshots")
+ .pop()
+ .expect("active session");
+ assert_eq!(snapshot.active_requests, 1);
+ let session = runtime
+ .sessions
+ .lock()
+ .expect("sessions lock")
+ .values()
+ .next()
+ .expect("session")
+ .session
+ .clone();
+ let request_id = session
+ .active_requests
+ .lock()
+ .expect("request lock")
+ .keys()
+ .next()
+ .expect("request ID")
+ .clone();
+ assert!(runtime.cancel_request(&request_id).expect("cancel request"));
+ assert!(task.await.expect("request task").is_err());
+ assert_eq!(
+ runtime.snapshots().expect("runtime snapshots")[0].active_requests,
+ 0
+ );
+ assert_eq!(
+ runtime.snapshots().expect("runtime snapshots")[0].consecutive_failures,
+ 0,
+ "operator cancellation must not degrade endpoint health"
+ );
+}
+
+#[test]
+fn telemetry_preview_is_privacy_reduced_and_checksummed() {
+ let preview = TallyRuntime::default()
+ .telemetry_preview()
+ .expect("telemetry preview");
+ assert_eq!(preview.schema, "bridge.tally.telemetry-preview/2");
+ assert_eq!(preview.payload_sha256.len(), 64);
+ let preview_value: serde_json::Value =
+ serde_json::from_str(&preview.preview_json).expect("valid preview JSON");
+ assert_eq!(
+ preview_value["privacy_profile"],
+ "fixed_dimensions_bucketed_values_v1"
+ );
+ assert_eq!(preview_value["authenticity_claim"], "none");
+}