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
54 changes: 53 additions & 1 deletion crates/batten/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,7 @@ pub fn parse(text: &str, source: &str) -> Result<Config> {
// it cannot read gets the chance to fail the parse, so the refusal says which
// engine to install rather than which field was unknown.
crate::engine::check(text, source, crate::engine::running_stamp)?;
let config = parse_ungated(text, source)?;
let config = parse_remembered(text, source)?;
check_min_version(&config, source)?;
// A WRITER of the forge declaration (CLOUD-1622). Every config LOAD funnels
// through here — `load`, `load_authority`, `load_site` — so recording it once
Expand Down Expand Up @@ -4232,6 +4232,34 @@ fn parse_ungated(text: &str, source: &str) -> Result<Config> {
parse_ungated_with(text, source, Grammar::Enforced)
}

/// [`parse_ungated`], answered from the last success when the bytes and the
/// source are the ones it parsed.
///
/// ONE ADJUDICATION PARSED THE SAME AUTHORITY THREE TIMES (CLOUD-2103):
/// `resolve` loads it twice and `epoch::authority` once more, each a full
/// deserialize and every load-time validator over 583 KB. Under callgrind that
/// was 435M of a 1,481M-instruction `adjudicate`. The result is a pure function
/// of `(text, source)` — the parse reads no environment and no file, and the one
/// load-time report, [`check_min_version`]'s, stays outside so it is still made
/// on every load. Only a success is kept: a refusal is re-derived, so its
/// message is never a stale one.
//MUTANT config-parse-unremembered|s@^ \&\& remembered_text == text$@ \&\& false@|one_authority_is_parsed_once_per_process
fn parse_remembered(text: &str, source: &str) -> Result<Config> {
static LAST: std::sync::Mutex<Option<(String, String, Config)>> = std::sync::Mutex::new(None);
if let Ok(last) = LAST.lock()
&& let Some((remembered_text, remembered_source, config)) = last.as_ref()
&& remembered_text == text
&& remembered_source == source
{
return Ok(config.clone());
}
let config = parse_ungated(text, source)?;
if let Ok(mut last) = LAST.lock() {
*last = Some((text.to_owned(), source.to_owned(), config.clone()));
}
Ok(config)
}

fn parse_ungated_with(text: &str, source: &str, grammar: Grammar) -> Result<Config> {
CONFIG_PARSES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
// THE COMMON CASE COSTS ONE PARSE, and it used to cost three.
Expand Down Expand Up @@ -6074,6 +6102,30 @@ mod tests {
);
}

/// CLOUD-2103: the same bytes from the same source are parsed once per
/// process, and anything else — other bytes, another source — is parsed.
#[test]
fn one_authority_is_parsed_once_per_process() {
let text = "version = 1\n# one-authority-is-parsed-once\n";
let first = parse(text, "first.toml").unwrap();
let before = config_parses();
let again = parse(text, "first.toml").unwrap();
assert_eq!(
config_parses(),
before,
"the same authority is not re-parsed"
);
assert_eq!(again, first, "and answers what the parse answered");
parse(text, "second.toml").unwrap();
assert_eq!(config_parses(), before + 1, "another source is parsed");
parse("version = 1\n# other bytes\n", "second.toml").unwrap();
assert_eq!(config_parses(), before + 2, "other bytes are parsed");
assert!(
parse("version = 1\nnot toml at all [", "bad.toml").is_err(),
"a refusal is still a refusal"
);
}

#[test]
fn a_present_authority_is_parsed_and_reported_present() {
let dir = std::env::temp_dir().join("batten-config-authority-present");
Expand Down
84 changes: 77 additions & 7 deletions crates/batten/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,13 +791,34 @@ pub fn checkout_fingerprint(repo_root: &std::path::Path) -> anyhow::Result<Finge
/// [`SpanNormalization::Collapsed`] — all whitespace removed.
#[must_use]
pub fn normalize_span(span: &str, mode: SpanNormalization) -> String {
let canonical: String = span.replace("\r\n", "\n").nfc().collect();
let canonical = canonical_text(span);
match mode {
SpanNormalization::Collapsed => canonical.chars().filter(|c| !c.is_whitespace()).collect(),
SpanNormalization::Verbatim => canonical,
SpanNormalization::Verbatim => canonical.into_owned(),
}
}

/// `CRLF -> LF` then NFC, borrowing the input when both are the identity.
///
/// THE SURFACE IS ALREADY CANONICAL, AND REWRITING IT WAS 37% OF AN ADJUDICATION
/// (CLOUD-2102). [`surface_fingerprint`] runs every policy file through here on
/// every hook call, and that text is LF and NFC by construction, so the two
/// rewrites produced the bytes they were given. Measured under callgrind on one
/// mcp-surface `adjudicate`: 719M of 2,087M instructions. Text with no `CRLF`
/// that the quick check calls NFC is returned as it is, which is exactly what
/// the rewrite returns for it; anything else — a `CRLF`, or a quick check
/// answering `No` or `Maybe` — takes the rewrite, and is the only text that does.
//MUTANT nfc-fast-path-never|s@^ if !span.contains("\\r\\n")$@ if false \&\& !span.contains("\\r\\n")@|normalized_text_is_fingerprinted_without_a_rewrite
fn canonical_text(span: &str) -> std::borrow::Cow<'_, str> {
if !span.contains("\r\n")
&& unicode_normalization::is_nfc_quick(span.chars())
== unicode_normalization::IsNormalized::Yes
{
return std::borrow::Cow::Borrowed(span);
}
std::borrow::Cow::Owned(span.replace("\r\n", "\n").nfc().collect())
}

/// Hash the kind tag plus each field, every part length-prefixed (u64 LE), so
/// field boundaries are injective: `("ab","c")` can never collide with
/// `("a","bc")`.
Expand Down Expand Up @@ -1314,19 +1335,22 @@ pub fn context_fingerprint(bytes: &[u8]) -> Fingerprint {
/// identity: adding an empty file still moves the value.
#[must_use]
pub fn surface_fingerprint(entries: &[(String, Vec<u8>)]) -> Fingerprint {
let normalized: Vec<(Vec<u8>, Vec<u8>)> = entries
let normalized: Vec<(&[u8], std::borrow::Cow<'_, [u8]>)> = entries
.iter()
.map(|(path, contents)| {
let content = match std::str::from_utf8(contents) {
Ok(text) => normalize_span(text, SpanNormalization::Verbatim).into_bytes(),
Err(_) => contents.clone(),
Ok(text) => match canonical_text(text) {
std::borrow::Cow::Borrowed(text) => std::borrow::Cow::Borrowed(text.as_bytes()),
std::borrow::Cow::Owned(text) => std::borrow::Cow::Owned(text.into_bytes()),
},
Err(_) => std::borrow::Cow::Borrowed(contents.as_slice()),
};
(path.as_bytes().to_vec(), content)
(path.as_bytes(), content)
})
.collect();
let fields: Vec<&[u8]> = normalized
.iter()
.flat_map(|(path, content)| [path.as_slice(), content.as_slice()])
.flat_map(|(path, content)| [*path, content.as_ref()])
.collect();
tagged_fingerprint(SURFACE_TAG, &fields)
}
Expand Down Expand Up @@ -1452,6 +1476,52 @@ pub const fn compare_to_anchor(anchor: u64, current: u64) -> CountChange {
mod tests {
use super::*;

/// CLOUD-2102: canonical text is borrowed, never rewritten, and every input
/// still canonicalizes to exactly what the full rewrite produces.
#[test]
fn normalized_text_is_fingerprinted_without_a_rewrite() {
let rewrite = |text: &str| -> String { text.replace("\r\n", "\n").nfc().collect() };
for canonical in ["", "plain ascii\nwith lines\n", "caf\u{e9} composed\n"] {
assert!(
matches!(canonical_text(canonical), std::borrow::Cow::Borrowed(_)),
"{canonical:?} is already canonical and is borrowed"
);
}
for text in [
"",
"plain ascii\nwith lines\n",
"caf\u{e9} composed\n",
"cafe\u{301} decomposed\n",
"crlf\r\nline\r\n",
"lone\rcarriage\n",
"mixed e\u{301}\r\n",
] {
assert_eq!(canonical_text(text), rewrite(text), "{text:?}");
assert_eq!(
normalize_span(text, SpanNormalization::Verbatim),
rewrite(text),
"{text:?}"
);
}
let entries = vec![
(String::from("a.rego"), b"package a\n".to_vec()),
(String::from("b.toml"), b"k = 1\r\n".to_vec()),
(String::from("c.bin"), vec![0xff, 0xfe]),
];
let expected = tagged_fingerprint(
SURFACE_TAG,
&[
b"a.rego",
b"package a\n",
b"b.toml",
b"k = 1\n",
b"c.bin",
&[0xff, 0xfe],
],
);
assert_eq!(surface_fingerprint(&entries), expected);
}

// -- CLOUD-594: the golden vectors. --
//
// Every other identity test in this module re-derives its expected value
Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ CI_VERDICT_STEPS = "Run mise run ,Run mise exec -- "
# which is a property of the world and belongs on a clock (`lock-complete`).
REGORUS_OPA_COMPLIANCE = "1.2.0"
REGORUS_OPA_COMPLIANCE_FOR = "0.11"
MUTANT_GATES = ".config/nextest.toml,engine-testing,crates/batten/tests/it/common/mod.rs,agentic-experiment-record,answer-the-operator,claude-code-cloud,engine-disk-watch,engine-prune,awk-regex,cap-drift,cfg-gated-test,ci-cache-declared,ci-hygiene,ci-parity,ci-slow-inert,ci-suite-lane,ci-tools,claim-before-code,claim-order-is-stated,coderabbit-config,commit-hygiene,dead-capability,denials-outlive-the-turn,digest-major-agreement,egress-fencing,engine-checks-green,engine-config,engine-doctor,engine-exec,engine-handler,engine-hook,engine-land,engine-landed,engine-lease,engine-lib,engine-mcp,engine-perf,engine-semver,engine-pinned,engine-pipeline,engine-policy,engine-ready,engine-speculation,engine-surface,engine-verdict,engine-wiring,filed-here,could-not-look-laundered,fixture-forks,forge-verdict-required,glob-containment,harness-grant,harness-wiring,hk-fix-selection,hk-plan-required,hook-pin-check,hook-skip-local,landing-loop,landing-roster-guarded,leased-push,license-table,lock-complete,mcp-timeout-budget,mise,mise-action-floor,mise-pin-agreement,module-map,msrv-pin-agreement,mutation-declared-case,nextest-slow,no-doctests,obligations-bound,perf-assert,pinned-toolchain,pipefail-grep,plan-complete,pr-partition-restated,pr-unsubscribed,privileged-lane,prose-only,publish-credential,release-due,release-provision-parity,release-tag-shape,remedy-authorship,repetition-without-progress,report-only,review-answered,review-dispatched,rules-paths-trigger,run-shape,rust-paths-check,sbom-inventory,shell-hygiene,shell-retirement,shell-write-advisory,skill-frontmatter-complete,spawn-widening,stop-posture,suite-subject-retirable,task-substitution,test-targets,timeout-budget,trunk-based,validator-verdict-clean,verdict-routes-resolve,weakens-declared,worktree-registration,engine-mutate,engine-task,run-arg-shape,engine-cargo-graph,branch-age,engine-released,evaluator-closure,evaluator-io-probe,agent-spawn,macos-link,ntia,release-tracking,sbom-actions,task-callable,transcript-corpus,engine-rules,release-assets,durable-write,spawn-factory,turn-ask,engine-forge,engine-forge-query,engine-git,engine-census,ci-signal,engine-ci-signal,docs-tree-absent,ripcord-untracked,hk-pin-agreement,engine-record,engine-suites,engine-admission,engine-ci-step,engine-gitwrite,engine-refusal,engine-repair,crates/batten/tests/it/mutant_rows.rs,hk-fix-selection.pkl,engine-dist,engine-sbom,supply-chain,engine-reclaim,engine-durable,engine-step,engine-step-table,engine-mcp-grant,engine-mcp-posture,engine-preflight,engine-trust,engine-sweep,sweep-exit-table,tracker-hygiene,engine-tracker-reading,task-duplicate-close-check,engine-release,release-hygiene,engine-hk,hook-profile,engine-attestation,engine-turn,engine-unsubscribe,engine-probe,finding-sink,engine-commit,engine-receipt,engine-board-check,check-verdict,engine-budget,engine-codemod,engine-remedy,engine-config-edit,engine-propose,crates/batten/tests/it/stub_portability.rs,crates/batten/tests/it/truncate_handle.rs,git,engine-engine,engine-attribution,crates/batten/tests/it/pointer_only.rs"
MUTANT_GATES = ".config/nextest.toml,engine-testing,crates/batten/tests/it/common/mod.rs,agentic-experiment-record,answer-the-operator,claude-code-cloud,engine-disk-watch,engine-prune,awk-regex,cap-drift,cfg-gated-test,ci-cache-declared,ci-hygiene,ci-parity,ci-slow-inert,ci-suite-lane,ci-tools,claim-before-code,claim-order-is-stated,coderabbit-config,commit-hygiene,dead-capability,denials-outlive-the-turn,digest-major-agreement,egress-fencing,engine-checks-green,engine-config,engine-doctor,engine-exec,engine-handler,engine-hook,engine-land,engine-landed,engine-lease,engine-lib,engine-mcp,engine-perf,engine-semver,engine-identity,engine-pinned,engine-pipeline,engine-policy,engine-ready,engine-speculation,engine-surface,engine-verdict,engine-wiring,filed-here,could-not-look-laundered,fixture-forks,forge-verdict-required,glob-containment,harness-grant,harness-wiring,hk-fix-selection,hk-plan-required,hook-pin-check,hook-skip-local,landing-loop,landing-roster-guarded,leased-push,license-table,lock-complete,mcp-timeout-budget,mise,mise-action-floor,mise-pin-agreement,module-map,msrv-pin-agreement,mutation-declared-case,nextest-slow,no-doctests,obligations-bound,perf-assert,pinned-toolchain,pipefail-grep,plan-complete,pr-partition-restated,pr-unsubscribed,privileged-lane,prose-only,publish-credential,release-due,release-provision-parity,release-tag-shape,remedy-authorship,repetition-without-progress,report-only,review-answered,review-dispatched,rules-paths-trigger,run-shape,rust-paths-check,sbom-inventory,shell-hygiene,shell-retirement,shell-write-advisory,skill-frontmatter-complete,spawn-widening,stop-posture,suite-subject-retirable,task-substitution,test-targets,timeout-budget,trunk-based,validator-verdict-clean,verdict-routes-resolve,weakens-declared,worktree-registration,engine-mutate,engine-task,run-arg-shape,engine-cargo-graph,branch-age,engine-released,evaluator-closure,evaluator-io-probe,agent-spawn,macos-link,ntia,release-tracking,sbom-actions,task-callable,transcript-corpus,engine-rules,release-assets,durable-write,spawn-factory,turn-ask,engine-forge,engine-forge-query,engine-git,engine-census,ci-signal,engine-ci-signal,docs-tree-absent,ripcord-untracked,hk-pin-agreement,engine-record,engine-suites,engine-admission,engine-ci-step,engine-gitwrite,engine-refusal,engine-repair,crates/batten/tests/it/mutant_rows.rs,hk-fix-selection.pkl,engine-dist,engine-sbom,supply-chain,engine-reclaim,engine-durable,engine-step,engine-step-table,engine-mcp-grant,engine-mcp-posture,engine-preflight,engine-trust,engine-sweep,sweep-exit-table,tracker-hygiene,engine-tracker-reading,task-duplicate-close-check,engine-release,release-hygiene,engine-hk,hook-profile,engine-attestation,engine-turn,engine-unsubscribe,engine-probe,finding-sink,engine-commit,engine-receipt,engine-board-check,check-verdict,engine-budget,engine-codemod,engine-remedy,engine-config-edit,engine-propose,crates/batten/tests/it/stub_portability.rs,crates/batten/tests/it/truncate_handle.rs,git,engine-engine,engine-attribution,crates/batten/tests/it/pointer_only.rs"
# The file inline tasks are declared in, for `mutate`'s `task-` route (CLOUD-1909).
# The crate may not spell a consumer's filename (non-negotiable rule 1), so the
# manifest is named here, beside the set it serves. Unset, a `task-` gate resolves
Expand Down
Loading