Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5dcaa7c
refactor(sql-analysis): make the binding catalog and schema validatio…
kvlonge Oct 13, 2026
2deff71
refactor(graph): run graph algorithms, gate edges and graph views nat…
kvlonge Oct 13, 2026
62c3c5b
refactor(references): resolve relation names, source relations and ma…
kvlonge Oct 13, 2026
967d658
refactor(fingerprints): hash queries and read and write fingerprint t…
kvlonge Oct 13, 2026
4862404
refactor(contracts): build model contract and promotion diagnostics n…
kvlonge Oct 13, 2026
2435518
refactor(contract-adoption): compare and edit adopted contracts natively
kvlonge Oct 13, 2026
3e55bd9
refactor(authored-values): validate named settings and render project…
kvlonge Oct 13, 2026
af3c6fc
refactor(refactoring): find Python strings naming a renamed model nat…
kvlonge Oct 13, 2026
f6d4de5
fix: register plan output, references and authored values together
kvlonge Oct 13, 2026
88ac372
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
085ed20
refactor: discover Python functions, hooks, modules and tolerant scop…
kvlonge Oct 13, 2026
9e62897
refactor: load, cache and tolerantly build the offline scope index na…
kvlonge Oct 13, 2026
545102a
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
5512b57
test: give the seed-state adapter mock a rendered fingerprint table name
kvlonge Oct 13, 2026
0efb68b
chore: keep executor runtime capture and event export in Python
kvlonge Oct 13, 2026
8502ec2
refactor: render execution-result documents and index terminals natively
kvlonge Oct 13, 2026
e9b4ce9
refactor: degrade build result documents natively
kvlonge Oct 13, 2026
908b4be
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
ba03375
refactor: dispatch sqb commands through a native route table
kvlonge Oct 13, 2026
e966b48
refactor: run the sqb entry, observed dispatch and error projection n…
kvlonge Oct 13, 2026
276edbe
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
38e304a
refactor(sql-test-glue): build compiled SQL test assemblies natively
kvlonge Oct 13, 2026
e335cb1
refactor(auditing): compute audit identities, measurements and audit …
kvlonge Oct 13, 2026
156ccd6
refactor(compile): resolve storage policies, cursor roles and input r…
kvlonge Oct 13, 2026
97bb404
refactor(compile): build model syntax checks and standalone pivot req…
kvlonge Oct 13, 2026
dd0c563
refactor(compile): scan and extract SQL references natively
kvlonge Oct 13, 2026
d3f7424
style: satisfy clippy in the native audit, SQL test and reference bin…
kvlonge Oct 13, 2026
e53f6ab
chore: update the Python boundary ratchet
kvlonge Oct 13, 2026
b4fef1b
test: expect the renamed native contract seams
kvlonge Oct 13, 2026
126bd04
test: flush pending writes before each timed compile benchmark run
kvlonge Oct 13, 2026
dbba844
test: bound the warm run after a project-wide rewrite separately
kvlonge Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions crates/sqlbuild-analysis/src/auditing/_helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub(crate) mod payloads;
175 changes: 175 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/_helpers/payloads.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! Python's audit identity payloads as JSON values, and their `json.dumps` SHA-256 hashes.

use sha2::{Digest, Sha256};
use sqlbuild_core::json::main::dumps::dumps;
use sqlbuild_core::json::models::{JsonDialect, JsonInteger, JsonValue, StdlibJsonOptions};

use crate::auditing::constants::{
ABOVE_OPERATOR, BELOW_OPERATOR, MEASUREMENT_MODE, OUTSIDE_OPERATOR,
};
use crate::auditing::models::{AuditIdentity, PlannedAudit, ThresholdBound, Thresholds};
use crate::sql_tokens::main::normalized_query_text::normalized_query_text;

fn text(value: &str) -> JsonValue {
JsonValue::String(value.to_owned())
}

fn optional_text(value: Option<&str>) -> JsonValue {
value.map_or(JsonValue::Null, text)
}

fn bound(bound: Option<ThresholdBound>) -> JsonValue {
let Some(bound) = bound else {
return JsonValue::Null;
};
let entries: Vec<(String, JsonValue)> = match bound {
ThresholdBound::Outside(lower, upper) => vec![
("operator".to_owned(), text(OUTSIDE_OPERATOR)),
("lower".to_owned(), JsonValue::Float(lower)),
("upper".to_owned(), JsonValue::Float(upper)),
],
ThresholdBound::Below(limit) => vec![
("operator".to_owned(), text(BELOW_OPERATOR)),
("limit".to_owned(), JsonValue::Float(limit)),
],
ThresholdBound::Above(limit) => vec![
("operator".to_owned(), text(ABOVE_OPERATOR)),
("limit".to_owned(), JsonValue::Float(limit)),
],
};
JsonValue::Object(entries)
}

fn thresholds(thresholds: Option<Thresholds>) -> JsonValue {
let Some(thresholds) = thresholds else {
return JsonValue::Null;
};
JsonValue::Object(vec![
("warn".to_owned(), bound(thresholds.warn)),
("error".to_owned(), bound(thresholds.error)),
])
}

/// `hash_payload`: SHA-256 of `json.dumps(payload, sort_keys=True, separators=(",", ":"))`.
pub(crate) fn hash_payload(payload: &JsonValue) -> String {
let options: StdlibJsonOptions = StdlibJsonOptions {
sort_keys: true,
..StdlibJsonOptions::new(None).with_separators(",", ":")
};
let encoded: String = dumps(payload, &JsonDialect::Stdlib(options)).unwrap_or_default();
format!("{:x}", Sha256::digest(encoded.as_bytes()))
}

/// The binding payload `audit_identity` hashes into the binding key.
fn binding_entries(audit: &PlannedAudit) -> Vec<(String, JsonValue)> {
let mut entries: Vec<(String, JsonValue)> = vec![
("audit_name".to_owned(), text(&audit.name)),
("attachment_kind".to_owned(), text(&audit.attachment_kind)),
(
"attached_target_name".to_owned(),
optional_text(audit.attached_target_name.as_deref()),
),
(
"attached_column_name".to_owned(),
optional_text(audit.attached_column_name.as_deref()),
),
("severity".to_owned(), text(&audit.severity)),
("run_scope_phase".to_owned(), text(&audit.run_scope)),
("always_run".to_owned(), JsonValue::Bool(audit.always_run)),
];
if let Some(measurement) = &audit.measurement {
entries.extend([
("evaluation_mode".to_owned(), text(MEASUREMENT_MODE)),
(
"value_column".to_owned(),
optional_text(measurement.value_column.as_deref()),
),
(
"sample_count_column".to_owned(),
optional_text(measurement.sample_count_column.as_deref()),
),
(
"sample_unit".to_owned(),
optional_text(measurement.sample_unit.as_deref()),
),
("thresholds".to_owned(), thresholds(measurement.thresholds)),
(
"minimum_samples".to_owned(),
measurement
.minimum_samples
.and_then(|count| JsonInteger::parse(&count.to_string()))
.map_or(JsonValue::Null, JsonValue::Integer),
),
]);
}
entries
}

fn optional_sql(sql: Option<&str>) -> JsonValue {
sql.map_or(JsonValue::Null, |sql| text(&normalized_query_text(sql)))
}

/// `audit_identity`: the binding key and definition and execution fingerprints of one audit.
pub(crate) fn audit_identity(index: usize, audit: &PlannedAudit) -> AuditIdentity {
let binding: Vec<(String, JsonValue)> = binding_entries(audit);
let mut definition: Vec<(String, JsonValue)> = binding.clone();
definition.push((
"unresolved_sql".to_owned(),
text(&normalized_query_text(&audit.unresolved_sql)),
));
let mut execution: Vec<(String, JsonValue)> = binding.clone();
execution.push((
"resolved_sql".to_owned(),
text(&normalized_query_text(&audit.resolved_sql)),
));
if let Some(measurement) = &audit.measurement {
definition.push((
"evidence_unresolved_sql".to_owned(),
optional_sql(measurement.evidence_unresolved_sql.as_deref()),
));
execution.push((
"evidence_resolved_sql".to_owned(),
optional_sql(measurement.evidence_resolved_sql.as_deref()),
));
}
AuditIdentity {
index,
binding_key: hash_payload(&JsonValue::Object(binding)),
definition_fingerprint: hash_payload(&JsonValue::Object(definition)),
execution_fingerprint: hash_payload(&JsonValue::Object(execution)),
}
}

/// `binding_payload`, plus the fingerprints when `with_fingerprints` (`execution_payload`).
pub(crate) fn identity_payload(
(identity, audit): (&AuditIdentity, &PlannedAudit),
with_fingerprints: bool,
) -> JsonValue {
let mut entries: Vec<(String, JsonValue)> = vec![
("binding_key".to_owned(), text(&identity.binding_key)),
("audit_name".to_owned(), text(&audit.name)),
("severity".to_owned(), text(&audit.severity)),
("run_scope_phase".to_owned(), text(&audit.run_scope)),
("attachment_kind".to_owned(), text(&audit.attachment_kind)),
(
"attached_target_name".to_owned(),
optional_text(audit.attached_target_name.as_deref()),
),
(
"attached_column_name".to_owned(),
optional_text(audit.attached_column_name.as_deref()),
),
("always_run".to_owned(), JsonValue::Bool(audit.always_run)),
];
if with_fingerprints {
entries.push((
"definition_fingerprint".to_owned(),
text(&identity.definition_fingerprint),
));
entries.push((
"execution_fingerprint".to_owned(),
text(&identity.execution_fingerprint),
));
}
JsonValue::Object(entries)
}
8 changes: 8 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Audit identity payload keys and the severity that blocks a gate.

/// The severity whose audits make up a gate's blocking set.
pub(crate) const BLOCKING_SEVERITY: &str = "error";
pub(crate) const MEASUREMENT_MODE: &str = "measurement";
pub(crate) const OUTSIDE_OPERATOR: &str = "outside";
pub(crate) const BELOW_OPERATOR: &str = "below";
pub(crate) const ABOVE_OPERATOR: &str = "above";
31 changes: 31 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/main/audit_gate_identity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! `build_audit_gate_identity`: a gate's binding and blocking set hashes.

use sqlbuild_core::json::models::JsonValue;

use crate::auditing::_helpers::payloads::{audit_identity, hash_payload, identity_payload};
use crate::auditing::constants::BLOCKING_SEVERITY;
use crate::auditing::models::{AuditIdentity, GateIdentity, PlannedAudit};

/// Hash the planned audits' bindings and blocking executions, sorted by binding key.
pub fn audit_gate_identity(audits: &[PlannedAudit]) -> GateIdentity {
let mut identities: Vec<AuditIdentity> = audits
.iter()
.enumerate()
.map(|(index, audit)| audit_identity(index, audit))
.collect();
identities.sort_by(|left, right| left.binding_key.cmp(&right.binding_key));
let mut bindings: Vec<JsonValue> = Vec::with_capacity(identities.len());
let mut blocking: Vec<JsonValue> = Vec::new();
for identity in &identities {
let audit: &PlannedAudit = &audits[identity.index];
bindings.push(identity_payload((identity, audit), false));
if audit.severity == BLOCKING_SEVERITY {
blocking.push(identity_payload((identity, audit), true));
}
}
GateIdentity {
binding_set_hash: hash_payload(&JsonValue::Array(bindings)),
blocking_set_hash: hash_payload(&JsonValue::Array(blocking)),
audits: identities,
}
}
40 changes: 40 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/main/evaluate_measurement.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! `evaluate_measurement`: the outcome of one measured value under its sample and threshold policy.

use crate::auditing::models::{MeasurementOutcome, ThresholdBound, Thresholds};

fn violates(measured: f64, bound: ThresholdBound) -> bool {
match bound {
ThresholdBound::Below(limit) => measured < limit,
ThresholdBound::Above(limit) => measured > limit,
ThresholdBound::Outside(lower, upper) => measured < lower || measured > upper,
}
}

/// The outcome of a finite measurement; `(sample_count, minimum_samples)` gate sufficiency.
pub fn evaluate_measurement(
measured: f64,
(sample_count, minimum_samples): (Option<i128>, Option<i128>),
thresholds: Thresholds,
) -> Result<MeasurementOutcome, &'static str> {
if !measured.is_finite() {
return Err("measured_value must be finite");
}
if let Some(minimum) = minimum_samples
&& sample_count.is_none_or(|count| count < minimum)
{
return Ok(MeasurementOutcome::Insufficient);
}
if thresholds
.error
.is_some_and(|bound| violates(measured, bound))
{
return Ok(MeasurementOutcome::Error);
}
if thresholds
.warn
.is_some_and(|bound| violates(measured, bound))
{
return Ok(MeasurementOutcome::Warn);
}
Ok(MeasurementOutcome::Pass)
}
3 changes: 3 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/main/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod audit_gate_identity;
pub mod evaluate_measurement;
pub mod replace_reference_calls;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//! Rewrite `function("name")` reference calls the way Python's `re.sub` over them does.

/// Replace each `function("name")` call, left to right without overlap; `None` keeps the call.
pub fn replace_reference_calls<E>(
sql: &str,
function: &str,
mut replace: impl FnMut(&str) -> Result<Option<String>, E>,
) -> Result<String, E> {
let opening: String = format!("{function}(\"");
let mut rewritten: String = String::with_capacity(sql.len());
let mut position: usize = 0;
let mut search: usize = 0;
while let Some(found) = sql[search..].find(&opening) {
let start: usize = search + found;
let name_start: usize = start + opening.len();
let Some(name_length) = sql[name_start..].find('"') else {
break;
};
let name_end: usize = name_start + name_length;
if name_length == 0 || !sql[name_end + 1..].starts_with(')') {
search = start + 1;
continue;
}
let end: usize = name_end + 2;
rewritten.push_str(&sql[position..start]);
match replace(&sql[name_start..name_end])? {
Some(replacement) => rewritten.push_str(&replacement),
None => rewritten.push_str(&sql[start..end]),
}
position = end;
search = end;
}
rewritten.push_str(&sql[position..]);
Ok(rewritten)
}
9 changes: 9 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Planned audit identities, measurement outcomes and reference-call rewriting for audit SQL.

pub(crate) mod _helpers;
mod constants;
pub mod main;
pub mod models;

#[cfg(test)]
mod tests;
71 changes: 71 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! A planned audit's identity inputs, its identity hashes and measurement policies.

/// One directional measurement threshold.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ThresholdBound {
Below(f64),
Above(f64),
/// Inclusive `(lower, upper)` range whose outside violates.
Outside(f64, f64),
}

/// A measurement audit's warn and error bounds.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Thresholds {
pub warn: Option<ThresholdBound>,
pub error: Option<ThresholdBound>,
}

/// The measurement facts a measurement audit's identity includes.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct MeasurementFacts {
pub value_column: Option<String>,
pub sample_count_column: Option<String>,
pub sample_unit: Option<String>,
pub thresholds: Option<Thresholds>,
pub minimum_samples: Option<i64>,
pub evidence_unresolved_sql: Option<String>,
pub evidence_resolved_sql: Option<String>,
}

/// One planned audit binding, as its identity reads it.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PlannedAudit {
pub name: String,
pub attachment_kind: String,
pub attached_target_name: Option<String>,
pub attached_column_name: Option<String>,
pub severity: String,
pub run_scope: String,
pub always_run: bool,
pub unresolved_sql: String,
pub resolved_sql: String,
/// Present for measurement audits.
pub measurement: Option<MeasurementFacts>,
}

/// The identity hashes of one planned audit; `index` is its position in the input.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuditIdentity {
pub index: usize,
pub binding_key: String,
pub definition_fingerprint: String,
pub execution_fingerprint: String,
}

/// A gate's binding and blocking set hashes, with its audits sorted by binding key.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GateIdentity {
pub binding_set_hash: String,
pub blocking_set_hash: String,
pub audits: Vec<AuditIdentity>,
}

/// The canonical outcome of one measured value.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MeasurementOutcome {
Pass,
Warn,
Error,
Insufficient,
}
6 changes: 6 additions & 0 deletions crates/sqlbuild-analysis/src/auditing/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[path = "tests/test_auditing.rs"]
mod auditing;
#[path = "tests/helpers.rs"]
mod helpers;
#[path = "tests/test_types.rs"]
mod test_types;
Loading
Loading