diff --git a/docs/proposed-rust-module-conventions.md b/docs/proposed-rust-module-conventions.md index 5e72dfcc..e137316d 100644 --- a/docs/proposed-rust-module-conventions.md +++ b/docs/proposed-rust-module-conventions.md @@ -89,7 +89,7 @@ argues for doing it where it helps and against treating it as risky. ## Where the general advice does not fit this repository This codebase has a constraint most advice does not consider: **files are -pinned**. `docs/tally/compatibility/compatibility-surface.json` binds 218 paths +pinned**. `docs/tally/compatibility/compatibility-surface.json` binds each pinned path by SHA-256, and the evidence receipts beneath it attest behaviour *of those bytes*. Splitting a pinned file is not a neutral move: diff --git a/docs/tally/compatibility/compatibility-matrix.json b/docs/tally/compatibility/compatibility-matrix.json index 7a55f200..ffc61449 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": "4dc0c7118372a4bc4a6b3b58346b0b113ce4b6bb07a154f208546f6cd43757a8", + "compatibility_surface_sha256": "17dcd4722234ab278f55d130b00151b71ae797f6a1a7cf18246f08ab82f21915", "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 4030a2a4..088f022f 100644 --- a/docs/tally/compatibility/compatibility-surface.json +++ b/docs/tally/compatibility/compatibility-surface.json @@ -341,6 +341,14 @@ "path": "src-tauri/src/agent_catalog.rs", "sha256": "55632c680704c2784545e422cf6847b81d2423191d651a0119d45188d9bf2671" }, + { + "path": "src-tauri/src/agent_change_parse.rs", + "sha256": "1459a3a732ed5f4e51c0b83481683ebbf26adb86b6c3cc10da8604ac03bd3823" + }, + { + "path": "src-tauri/src/agent_changes.rs", + "sha256": "ce8c0ca98c2194f8ce42493a39f3a034f80418bcf4c6aa3d9fc9df30a6161f71" + }, { "path": "src-tauri/src/agent_company.rs", "sha256": "c719ff31cb8e14959dcaecd851156356cc7e38fe50a07aaf0dd339cfd78641dc" @@ -385,6 +393,18 @@ "path": "src-tauri/src/agent_ledgers.rs", "sha256": "a891e4ac51addc9dc17ae28ce4634bd2cfeb8f3f0df78e072290771d29a3131b" }, + { + "path": "src-tauri/src/agent_movement.rs", + "sha256": "a1ca27209a17a23db26b72d190122b8be49a930b55151ecea59913126b599185" + }, + { + "path": "src-tauri/src/agent_movement_math.rs", + "sha256": "5047954d7f0e5c7adfc31241bab4108a734f1f815102e95f78f080a66228bdf9" + }, + { + "path": "src-tauri/src/agent_outstandings.rs", + "sha256": "93538acb8e43a1a02891d0cc5b9e8615daf10d4493eebe18cc431cc3425c7fc3" + }, { "path": "src-tauri/src/agent_presence.rs", "sha256": "0bfeb05248b0d371e6a87b4b004c9c3815b5590017c6bd3231277db12b030c8d" @@ -405,6 +425,10 @@ "path": "src-tauri/src/agent_read_validation.rs", "sha256": "7c1bf2bc330b9703e0b2ae791ae7d777b6c64449395ff79b7ef855ee0dafdfeb" }, + { + "path": "src-tauri/src/agent_receipt_fields.rs", + "sha256": "cbdd09b7916fe5b96c7be4cecab9e0bdf2f0cdb018148c106a83e026a9128ada" + }, { "path": "src-tauri/src/agent_responses.rs", "sha256": "dfa582e79dab46315de88ba68c7dca069566da20b67733558a38cdd714f5c3b4" @@ -895,7 +919,7 @@ }, { "path": "tools/bridge-tally-compatibility/src/lib.rs", - "sha256": "8f9840b107e3c14290e66a7e9eeafce09930fb7d17286a35c9475d1af87b6062" + "sha256": "8ad5d452bb3f76e3d1ee69c78c0a564af2263392ffb0c47f58ed49c837eb6692" }, { "path": "tools/bridge-tally-compatibility/src/main.rs", @@ -930,5 +954,5 @@ "sha256": "a8ac2714fecf51947f2822c8c46d7ce2e8602c732780ff60566a7771f0836f9a" } ], - "manifest_sha256": "4dc0c7118372a4bc4a6b3b58346b0b113ce4b6bb07a154f208546f6cd43757a8" + "manifest_sha256": "17dcd4722234ab278f55d130b00151b71ae797f6a1a7cf18246f08ab82f21915" } \ No newline at end of file diff --git a/src-tauri/tests/admission_and_egress_files_stay_pinned.rs b/src-tauri/tests/admission_and_egress_files_stay_pinned.rs index 6296dc48..befac991 100644 --- a/src-tauri/tests/admission_and_egress_files_stay_pinned.rs +++ b/src-tauri/tests/admission_and_egress_files_stay_pinned.rs @@ -1,11 +1,11 @@ -//! The fourteen files pinned by the raise to 232 (bridge#416) must stay pinned. +//! The files pinned by the raises to 232 and 238 (bridge#416) must stay pinned. //! //! The compatibility gate cannot notice a pin disappearing. `rehash-surface` //! updates hashes and never adds paths. `docs/release-process.md` requires the //! pin list to be merged rather than resolved by taking one side; a resolution //! that takes the base side anyway drops every entry a branch added while //! keeping the raised `MAX_SURFACE_FILES`, and the gate passes. So does the cap -//! assertion, which bounds headroom and would pass with all fourteen dropped. +//! assertion, which bounds headroom and would pass with all of them dropped. //! `book_presence_tests.rs` guards its own contract's pins the same way. //! //! This file is deliberately not pinned itself: a guard that lived in the @@ -35,6 +35,17 @@ const ADMISSION_AND_EGRESS: [&str; 14] = [ "src-tauri/src/tally/runtime_control.rs", ]; +/// The six files pinned by the raise to 238: five on the read path, and the +/// egress receipt's field walker. Reasons live beside `MAX_SURFACE_FILES`. +const READ_PATH_AND_RECEIPT: [&str; 6] = [ + "src-tauri/src/agent_change_parse.rs", + "src-tauri/src/agent_changes.rs", + "src-tauri/src/agent_movement.rs", + "src-tauri/src/agent_movement_math.rs", + "src-tauri/src/agent_outstandings.rs", + "src-tauri/src/agent_receipt_fields.rs", +]; + fn pinned_paths(surface: &str) -> BTreeSet { let surface: serde_json::Value = serde_json::from_str(surface).expect("surface json"); surface["files"] @@ -53,9 +64,8 @@ fn unpinned<'a>(pinned: &BTreeSet, required: &[&'a str]) -> Vec<&'a str> .collect() } -#[test] -fn admission_and_egress_files_are_still_pinned() { - let missing = unpinned(&pinned_paths(SURFACE), &ADMISSION_AND_EGRESS); +fn assert_still_pinned(required: &[&str]) { + let missing = unpinned(&pinned_paths(SURFACE), required); assert!( missing.is_empty(), "dropped from the compatibility surface: {missing:?}. A merge that took \ @@ -65,17 +75,29 @@ fn admission_and_egress_files_are_still_pinned() { ); } +#[test] +fn admission_and_egress_files_are_still_pinned() { + assert_still_pinned(&ADMISSION_AND_EGRESS); +} + +#[test] +fn read_path_and_receipt_files_are_still_pinned() { + assert_still_pinned(&READ_PATH_AND_RECEIPT); +} + /// The check above must be able to fail. Drive the same two functions over the /// real surface with one entry removed, rather than a hand-built fixture that /// would only prove `BTreeSet::contains` works. #[test] fn the_pin_check_reports_a_dropped_entry() { - let dropped = ADMISSION_AND_EGRESS[0]; - let mut surface: serde_json::Value = serde_json::from_str(SURFACE).expect("surface json"); - surface["files"] - .as_array_mut() - .expect("surface files") - .retain(|entry| entry["path"].as_str() != Some(dropped)); - let pinned = pinned_paths(&surface.to_string()); - assert_eq!(unpinned(&pinned, &ADMISSION_AND_EGRESS), vec![dropped]); + for required in [&ADMISSION_AND_EGRESS[..], &READ_PATH_AND_RECEIPT[..]] { + let dropped = required[0]; + let mut surface: serde_json::Value = serde_json::from_str(SURFACE).expect("surface json"); + surface["files"] + .as_array_mut() + .expect("surface files") + .retain(|entry| entry["path"].as_str() != Some(dropped)); + let pinned = pinned_paths(&surface.to_string()); + assert_eq!(unpinned(&pinned, required), vec![dropped]); + } } diff --git a/tools/bridge-tally-compatibility/src/lib.rs b/tools/bridge-tally-compatibility/src/lib.rs index 76c2509f..5be9073a 100644 --- a/tools/bridge-tally-compatibility/src/lib.rs +++ b/tools/bridge-tally-compatibility/src/lib.rs @@ -31,7 +31,7 @@ pub const RESERVED_SURFACE_FILES: usize = 15; /// and manifest) but makes further unreviewed additions an explicit /// compatibility-surface decision. /// -/// **Raised six times, the first three by branches that did not see each +/// **Raised seven times, the first three by branches that did not see each /// other.** 210 to 211 on master for `src-tauri/src/agent_ledgers.rs`, 211 to /// 212 for `src-tauri/crates/bridge-tally-core/src/master_binding.rs`, 212 to /// 216 in a single commit for the voucher-presence engine, its adapter, its @@ -39,8 +39,9 @@ pub const RESERVED_SURFACE_FILES: usize = 15; /// taking the slot a paragraph below had already reserved for it by name, which /// is why the four pins arrive as one raise and not two -- 216 to 217 for /// `.github/workflows/dependency-security-scheduled.yml`, 217 to 218 for -/// `src-tauri/src/agent_import_identity.rs`, and 218 to 232 for fourteen files -/// named individually below. Each reason stands; a merge that +/// `src-tauri/src/agent_import_identity.rs`, 218 to 232 for fourteen files +/// named individually below, and 232 to 238 for six more. Each reason stands; a +/// merge that /// keeps a raise but loses its pin would pass the gate with behavior silently /// outside the evidence boundary, which is the failure this constant exists to /// make loud. @@ -121,13 +122,45 @@ pub const RESERVED_SURFACE_FILES: usize = 15; /// the response hash it commits to, and that only a persisted preparation /// yields a write-completion token. /// +/// The raise to 238 binds six more files from the same bridge#416 search. Five +/// decide what a read tells a caller, or when a sync may move past rows. The +/// sixth, `agent_receipt_fields.rs`, belongs with the egress record above: +/// bridge#416 listed it as borderline rather than with the fourteen, and it +/// describes the response of any tool, not only a read. Same rule: each reason +/// says what the file holds. +/// +/// What a read reports, or lets a sync skip: +/// - `agent_movement.rs` -- the ledger movement tool, and the voucher predicate +/// it applies after checking the whole window: cancelled, optional and +/// entryless vouchers are left out of the movement figures. +/// - `agent_movement_math.rs` -- one ledger's movement row: when an opening was +/// observed, closing is opening plus debit plus credit; when none was, closing +/// is left empty and the row is marked `partial` with +/// `opening_balance_not_observed`. +/// - `agent_outstandings.rs` -- the outstandings tool's receivable and payable +/// totals over open bills, and its ageing buckets (0-30, 31-60, 61-90, over 90 +/// days, and unaged). +/// - `agent_change_parse.rs` -- `checkpoint_advanceable`: true when a page was +/// not truncated and its highest returned alter id, or the requested +/// checkpoint when it returned none, reaches the company's high-water mark. +/// - `agent_changes.rs` -- the changed-since tool, which applies that predicate +/// to vouchers and masters separately, reports `checkpoint_advanceable` only +/// when both hold, chooses each axis's next alter id from its own result, and +/// refuses a checkpoint past the company snapshot. +/// +/// And the egress record: +/// - `agent_receipt_fields.rs` -- `released_fields`, which the egress receipt in +/// `agent_delivery.rs` uses to describe a released tool response by its JSON +/// key paths rather than its values. +/// /// Not pinned, and deliberately: files feature-gated out of every shipped build /// (`agent_lab.rs`, `jsonex*.rs`, `india_tax_observation.rs`), operator filing -/// labels, dead or declaration-only modules, and the read-path files that -/// compute reported figures or decide when a change cursor may advance. Those -/// decide what a read says, not what is admitted or where data may go; they are -/// the next candidates if the boundary widens, and bridge#416 records why. -pub const MAX_SURFACE_FILES: usize = 232; +/// labels, dead or declaration-only modules, and `observability.rs`. Its count +/// bucketing is a real privacy reduction, and the `tally_telemetry_preview` +/// command returns what it builds, but nothing in the frontend calls that +/// command and nothing sends its result off the machine. It becomes a candidate +/// when something does. bridge#416 records the reasoning for the rest. +pub const MAX_SURFACE_FILES: usize = 238; pub const MAX_OPERATIONS: usize = 16; pub const MAX_CLAIMS: usize = 128; pub const MAX_KEYS: usize = 32;