Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/tally/compatibility/compatibility-matrix.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"bridge_commit_sha": "be1c20cc3fd66fa1ece196505c69f26e555e4b8e",
"compatibility_surface_sha256": "4af73dde3130e886e54d969d1bf66f32f6697ba31c560353ebe90ae56050031c",
"compatibility_surface_sha256": "540328895ce1d619208a573bb3a2b834ac73bb416b6cff7ba5d52a18bce04d32",
"claims": [
{
"claim_id": "erp9-6-6-3-windows-education-xml-one-company",
Expand Down
6 changes: 3 additions & 3 deletions docs/tally/compatibility/compatibility-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@
},
{
"path": "src-tauri/src/tally/connection.rs",
"sha256": "18512da8f2b7d9cb300a8ffa638e50b7bab0d56dc51db208685fc77fab2085f0"
"sha256": "4101249e083862e8ec5a1d9252a20a724ae88db0dbdee5a71625d89e5bf29616"
},
{
"path": "src-tauri/src/tally/connector.rs",
Expand All @@ -331,7 +331,7 @@
},
{
"path": "src-tauri/src/tally/runtime.rs",
"sha256": "05a349822619d2ba46695d21fd46bd6ebd0c767e6e6901ff691c7990c4427c91"
"sha256": "38e7f9510e860de33e7051a0e51c7db09dfea2738d5c89e04fa491cbdc5011f9"
},
{
"path": "src-tauri/src/tally/serial_queue.rs",
Expand Down Expand Up @@ -362,5 +362,5 @@
"sha256": "5a5c6eaaba234c3cbda52dfa040ed3314f79e535f744b87bc14d8d76a2299811"
}
],
"manifest_sha256": "4af73dde3130e886e54d969d1bf66f32f6697ba31c560353ebe90ae56050031c"
"manifest_sha256": "540328895ce1d619208a573bb3a2b834ac73bb416b6cff7ba5d52a18bce04d32"
}
142 changes: 138 additions & 4 deletions src-tauri/src/tally/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ use bridge_tally_core::{
};
use bridge_tally_protocol::{
parse_companies_for_interactive_discovery, parse_ledger_source_records_with_evidence,
parse_selected_voucher_source_records_with_evidence, parse_standard_ledger_catalog,
parse_standard_ledger_identity_observation, verify_selected_voucher_window_context,
parse_ledger_write_readback_with_evidence, parse_selected_voucher_source_records_with_evidence,
parse_standard_ledger_catalog, parse_standard_ledger_identity_observation,
verify_company_context, verify_selected_voucher_window_context,
xml_read_profiles::{
ReadOnlyProfile, ValidatedCanaryLedgerName, ValidatedCompanyName,
ValidatedIdentityQuerySha256,
},
TallyTextEncoding, BRIDGE_LEDGER_EXPORT_SCHEMA, BRIDGE_SELECTED_VOUCHER_EXPORT_SCHEMA,
};
use bridge_tally_transport::{
Expand All @@ -29,6 +34,30 @@ use bridge_tally_transport::{

pub type TallyConfig = TallyEndpointConfig;

/// An exact, validated write-canary readback. Its XML remains crate-private to
/// the future write coordinator and is never returned to the UI or persisted.
#[allow(
dead_code,
reason = "the sealed runtime seam is intentionally staged before the write coordinator"
)]
pub(crate) struct LedgerCanaryReadbackXml(String);

impl LedgerCanaryReadbackXml {
#[allow(
dead_code,
reason = "only the future crate-internal write coordinator may inspect sealed XML"
)]
pub(crate) fn as_xml(&self) -> &str {
&self.0
}
}

impl std::fmt::Debug for LedgerCanaryReadbackXml {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("LedgerCanaryReadbackXml([redacted])")
}
}

#[derive(Debug, Clone, Serialize)]
pub enum TallyProduct {
TallyPrime,
Expand Down Expand Up @@ -477,6 +506,38 @@ impl TallyClient {
Ok(parsed.records)
}

/// Executes the closed canary-readback profile and admits its response only
/// when the company, query commitment, and at-most-one exact ledger agree.
#[allow(
dead_code,
reason = "the sealed runtime seam is intentionally staged before the write coordinator"
)]
pub(crate) async fn fetch_ledger_canary_readback(
&self,
company: ValidatedCompanyName,
ledger_name: ValidatedCanaryLedgerName,
identity_query_sha256: ValidatedIdentityQuerySha256,
expected_company_guid: &str,
) -> anyhow::Result<LedgerCanaryReadbackXml> {
let xml = self
.post_xml(
ReadOnlyProfile::LedgerCanaryReadbackV1 {
company: &company,
ledger_name: &ledger_name,
identity_query_sha256: &identity_query_sha256,
}
.render(),
)
.await?;
validate_ledger_canary_readback(
&xml,
ledger_name.as_str(),
identity_query_sha256.as_str(),
expected_company_guid,
)?;
Ok(LedgerCanaryReadbackXml(xml))
}

/// Reads the documented standard ledger collection as an explicitly limited
/// compatibility catalog. It is not a fallback for Bridge's custom export
/// and cannot establish snapshot, voucher, or write capability.
Expand Down Expand Up @@ -718,6 +779,40 @@ fn validate_selected_ledgers(
Ok(())
}

#[allow(
dead_code,
reason = "the sealed runtime seam is intentionally staged before the write coordinator"
)]
fn validate_ledger_canary_readback(
xml: &str,
expected_ledger_name: &str,
expected_identity_query_sha256: &str,
expected_company_guid: &str,
) -> anyhow::Result<()> {
let parsed = parse_ledger_write_readback_with_evidence(xml)?;
verify_company_context(&parsed.evidence, expected_company_guid)?;
if parsed
.evidence
.company_context
.as_ref()
.and_then(|context| context.query_identity_set_sha256.as_deref())
!= Some(expected_identity_query_sha256)
{
anyhow::bail!("Tally canary readback query commitment did not match the request");
}
if parsed.records.len() > 1 {
anyhow::bail!("Tally canary readback returned more than one ledger");
}
if parsed
.records
.first()
.is_some_and(|record| record.record.name != expected_ledger_name)
{
anyhow::bail!("Tally canary readback ledger name did not match the request");
}
Ok(())
}

fn verify_selected_company_name(
evidence: &bridge_tally_protocol::ExportEvidence,
expected_name: &str,
Expand Down Expand Up @@ -797,8 +892,8 @@ fn detect_product(text: &str) -> TallyProduct {
mod tests {
use super::{
canonical_loopback_origin, decode_xml_bytes, detect_product,
normalize_discovered_companies, tally_endpoint, unique_company_guids, TallyClient,
TallyConfig, TallyProduct,
normalize_discovered_companies, tally_endpoint, unique_company_guids,
validate_ledger_canary_readback, TallyClient, TallyConfig, TallyProduct,
};
use bridge_tally_core::{
CapabilityFeatureId, CapabilityPackId, CapabilityState, EvidenceConfidence, TransportId,
Expand All @@ -807,6 +902,45 @@ mod tests {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

const CANARY_QUERY_DIGEST: &str =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

fn canary_readback(ledger_name: &str, company_guid: &str, query_digest: &str) -> String {
format!(
r#"<ENVELOPE><HEADER><STATUS>1</STATUS></HEADER><BODY><COMPANYCONTEXT SCHEMA="bridge.tally.ledger-write-readback/1" OBJECTTYPE="LEDGER" NAME="BRIDGE SYNTHETIC BOOK" GUID="{company_guid}" RECORDCOUNT="1" QUERYIDENTITYSETSHA256="{query_digest}"/><LEDGER REMOTEID="bridge-canary-remote-id" NAME="{ledger_name}"><PARENT>BRIDGE SYNTHETIC GROUP</PARENT><OPENINGBALANCE>0</OPENINGBALANCE></LEDGER></BODY></ENVELOPE>"#
)
}

#[test]
fn canary_readback_requires_exact_company_commitment_and_ledger_name() {
let ledger_name = "BRIDGE-CANARY-LEDGER-001";
let xml = canary_readback(ledger_name, "company-guid", CANARY_QUERY_DIGEST);
validate_ledger_canary_readback(&xml, ledger_name, CANARY_QUERY_DIGEST, "company-guid")
.expect("exact synthetic canary readback is accepted");

assert!(validate_ledger_canary_readback(
&xml,
"BRIDGE-CANARY-LEDGER-002",
CANARY_QUERY_DIGEST,
"company-guid",
)
.is_err());
assert!(validate_ledger_canary_readback(
&xml,
ledger_name,
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"company-guid",
)
.is_err());
assert!(validate_ledger_canary_readback(
&xml,
ledger_name,
CANARY_QUERY_DIGEST,
"other-company-guid",
)
.is_err());
}

#[test]
fn detects_tallyprime_status() {
assert!(matches!(
Expand Down
46 changes: 45 additions & 1 deletion src-tauri/src/tally/runtime.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use super::{ConnectionStatus, TallyClient, TallyCompany, TallyConfig, TallyLedger};
use super::{TallyProbeResult, TallyVoucher};
use crate::tally::connection::{canonical_loopback_origin, SelectedReadObservation};
use crate::tally::connection::{
canonical_loopback_origin, LedgerCanaryReadbackXml, SelectedReadObservation,
};
use crate::tally::connector::SealedReadRequest;
use bridge_tally_protocol::xml_read_profiles::{
ValidatedCanaryLedgerName, ValidatedCompanyName, ValidatedIdentityQuerySha256,
};
use bridge_tally_runtime::{
BodyBytesObservation, EndpointCircuitState, EndpointIdentity, EndpointRuntimeSnapshot,
PortableReadRuntime, ReadAttempt, ReadExecutionError, ReadFailureClass, ReadOperation,
Expand Down Expand Up @@ -803,6 +808,45 @@ impl TallyRuntime {
.await
}

/// Executes one sealed, serial canary readback. This remains a read-only
/// internal primitive for the future write coordinator; it never exposes
/// response XML to commands, the UI, or persistence.
#[allow(
dead_code,
reason = "the sealed runtime seam is intentionally staged before the write coordinator"
)]
pub(crate) async fn fetch_ledger_canary_readback(
&self,
config: TallyConfig,
company: ValidatedCompanyName,
ledger_name: ValidatedCanaryLedgerName,
identity_query_sha256: ValidatedIdentityQuerySha256,
expected_company_guid: String,
) -> anyhow::Result<LedgerCanaryReadbackXml> {
self.execute(
config,
ReadOperation::MasterExport,
ReadRetryPolicy::SINGLE_ATTEMPT,
move |client| {
let company = company.clone();
let ledger_name = ledger_name.clone();
let identity_query_sha256 = identity_query_sha256.clone();
let expected_company_guid = expected_company_guid.clone();
async move {
client
.fetch_ledger_canary_readback(
company,
ledger_name,
identity_query_sha256,
&expected_company_guid,
)
.await
}
},
)
.await
}

pub async fn qualify_selected_ledgers(
&self,
config: TallyConfig,
Expand Down