///|
/// Declared provenance, not authentication. Baselines must still be trusted.
pub struct ReviewOrigin {
project : String
tool_version : String
rules_version : String
database : String
}
///|
/// Version-2 durable review. One file is represented as a one-entry batch.
/// All execution/policy facts are retained in batch; legacy v1 APIs are unchanged.
pub struct ReviewArchive {
origin : ReviewOrigin
batch : BatchReport
}
///|
fn origin_text(text : String) -> Bool {
!text.is_empty() &&
text.length() <= 256 &&
valid_unicode(text) &&
text.trim().length() == text.length() &&
!text.code_units().any(c => c < 32 || c == 127)
}
///|
fn ReviewOrigin::to_json(self : ReviewOrigin) -> Json {
{
"project": self.project,
"tool_version": self.tool_version,
"rules_version": self.rules_version,
"database": self.database,
}
}
///|
/// Analyze inputs now; never stamp provenance onto an old unverified report.
/// Project is a stable caller-assigned identifier, 1..256 UTF-16 units.
pub fn ReviewArchive::review(
project : String,
inputs : ArrayView[Migration],
initial_lock_timeout_ms? : Int32,
) -> Result[ReviewArchive, String] {
if !origin_text(project) {
return Err(
"Project must be 1..256 Unicode UTF-16 units without surrounding whitespace or control characters",
)
}
for i, input in inputs {
if !valid_unicode(input.path) || !valid_unicode(input.source) {
return Err("Input \{i} path/source contains an unpaired UTF-16 surrogate")
}
}
Ok({
origin: {
project,
tool_version: "0.1.0",
rules_version: "pg18-review-1",
database: "postgresql-18",
},
batch: review_migrations(inputs, initial_lock_timeout_ms?),
})
}
///|
pub fn ReviewArchive::exit_code(self : ReviewArchive) -> Int32 {
self.batch.exit_code()
}
///|
pub fn ReviewArchive::to_json(self : ReviewArchive) -> Json {
{
"format_version": 2,
"origin": self.origin.to_json(),
"batch": self.batch.to_json(),
}
}
///|
pub fn ReviewArchive::to_text(self : ReviewArchive) -> String {
"Project: \{self.origin.project}; tool=\{self.origin.tool_version}; rules=\{self.origin.rules_version}; database=\{self.origin.database}\n" +
self.batch.to_text()
}
///|
/// Strict restoration without reanalysis. Unknown origins may be viewed but
/// comparison requires a compatible PostgreSQL origin. v1 is never upgraded by
/// guessing: use Report/BatchReport::parse_json to view it, or regenerate it.
pub fn ReviewArchive::parse_json(
source : String,
) -> Result[ReviewArchive, String] {
if !valid_unicode(source) {
return Err("Archive JSON contains invalid Unicode")
}
let value = @json.parse(
source,
reject_duplicate_keys=true,
preserve_number_repr=true,
max_nesting_depth=20,
) catch {
e => return Err("Invalid archive JSON: \{Repr(e)}")
}
guard value is Object(root) &&
root.length() == 3 &&
value is { "format_version": version, "origin": origin, "batch": saved, .. } else {
return Err(
"Expected v2 origin/batch archive; legacy v1 lacks provenance: view with Report/BatchReport::parse_json or regenerate the review",
)
}
guard report_integer(version, "format_version") is Ok(2) else {
return Err(
"Archive format_version must be the plain integer 2; legacy reports must be regenerated for checked comparison",
)
}
guard origin is Object(fields) &&
fields.length() == 4 &&
origin
is {
"project": String(project),
"tool_version": String(tool_version),
"rules_version": String(rules_version),
"database": String(database),
..
} else {
return Err(
"Origin requires exactly project, tool_version, rules_version and database strings",
)
}
for text in [project, tool_version, rules_version, database] {
if !origin_text(text) {
return Err(
"Origin strings must be 1..256 Unicode UTF-16 units without surrounding whitespace or control characters",
)
}
}
let batch = match BatchReport::parse_json(saved.stringify()) {
Ok(batch) => batch
Err(e) => return Err("Archive batch: " + e)
}
Ok({ origin: { project, tool_version, rules_version, database, }, batch, })
}
///|
pub fn ReviewArchive::to_sarif(self : ReviewArchive) -> Result[Json, String] {
let log = match self.batch.to_sarif() {
Ok(log) => log
Err(e) => return Err(e)
}
guard log is Object(root) && root.get("runs") is Some(Array(runs)) else {
return Err("Unexpected SARIF envelope")
}
for run in runs {
guard run is Object(fields) &&
fields.get("tool") is Some({ "driver": Object(driver), .. }) else {
return Err("Unexpected SARIF tool")
}
driver["version"] = Json(self.origin.tool_version)
guard fields.get("properties") is Some(Object(properties)) else {
return Err("Unexpected SARIF properties")
}
properties["migrationLensOrigin"] = self.origin.to_json()
}
Ok(log)
}