///|
priv struct ScopedAcquisitionRule {
id : String
query : ScanQuery
expected : MetadataExpectations
}
///|
/// Every declared rule must match scans; every indexed scan must be covered.
/// Overlaps are conjunctive, never first/last-rule-wins.
pub struct AcquisitionRules {
priv rules : Array[ScopedAcquisitionRule]
}
///|
fn AcquisitionRules::rules_value(self : AcquisitionRules) -> Json {
Json::object({
"rules": Json::array(
self.rules.map(rule => {
Json::object({
"id": Json::string(rule.id),
"select": rule.query.query_value(),
"expect": rule.expected.expectations_value(),
})
}),
),
})
}
///|
pub fn AcquisitionRules::to_json(self : AcquisitionRules) -> String {
canonical_json(self.rules_value())
}
///|
pub fn AcquisitionRules::from_json(
text : String,
) -> AcquisitionRules raise TraceError {
guard parse_source("", text, container_limit=70)
is Object(input) else {
reject(
"invalid_acquisition_rules", "", "Expected a nonempty rules array.",
)
abort("reject raises")
}
for key in sorted_keys(input) {
if key != "rules" {
reject(
"unknown_acquisition_rule_field", key, "Use only the rules control.",
)
}
}
guard input.get("rules") is Some(Array(values)) &&
!values.is_empty() &&
values.length() <= 64 else {
reject(
"invalid_acquisition_rules", "", "Declare between 1 and 64 scoped rules.",
)
abort("reject raises")
}
let seen : Map[String, String] = Map([])
let rules = values.map(value => {
guard value is Object(fields) else {
reject(
"invalid_acquisition_rule", "/rules", "Each rule requires id, select and expect.",
)
abort("reject raises")
}
for key in sorted_keys(fields) {
if !["id", "select", "expect"].contains(key) {
reject(
"unknown_acquisition_rule_field", key, "Rule controls are exactly id, select and expect.",
)
}
}
guard fields.get("id") is Some(String(id)) &&
fields.get("select") is Some(query) &&
fields.get("expect") is Some(expected) else {
reject(
"invalid_acquisition_rule", "/rules", "Each rule requires string id plus select and expect objects.",
)
abort("reject raises")
}
if id.is_empty() ||
id.length() > 64 ||
!id
.iter()
.all(c => {
match c {
'a'..<='z' | 'A'..<='Z' | '0'..<='9' | '_' | '-' => true
_ => false
}
}) {
reject(
"invalid_rule_id", id, "Use 1–64 ASCII letters, digits, underscores or hyphens for a rule id.",
)
}
if seen.get(id.to_lower()) is Some(existing) {
reject(
"duplicate_rule_id",
id,
"Rule ids must be unique including case variants.",
related=[existing],
)
}
seen[id.to_lower()] = id
{
id,
query: ScanQuery::from_json(canonical_json(query)),
expected: MetadataExpectations::from_json(canonical_json(expected)),
}
})
rules.sort_by((a, b) => lexical_compare(a.id, b.id))
let result : AcquisitionRules = { rules, }
if result.to_json().length() > 2097152 {
reject(
"input_limit", "", "Canonical rules exceed 2,097,152 UTF-16 units; reduce the review scope.",
)
}
result
}
///|
pub struct AcquisitionReport {
priv outcome : ExpectationDecision
priv violations : Int32
priv errors : Int32
priv uncovered : Array[String]
priv unmatched : Array[String]
priv document : Json
}
///|
pub fn AcquisitionReport::decision(
self : AcquisitionReport,
) -> ExpectationDecision {
self.outcome
}
///|
/// Failed rule/field checks; a scan covered by multiple rules can contribute to each.
pub fn AcquisitionReport::violation_count(self : AcquisitionReport) -> Int32 {
self.violations
}
///|
/// Unresolved indexed scans, counted once irrespective of rule overlap.
pub fn AcquisitionReport::error_count(self : AcquisitionReport) -> Int32 {
self.errors
}
///|
pub fn AcquisitionReport::uncovered_paths(
self : AcquisitionReport,
) -> Array[String] {
self.uncovered.copy()
}
///|
pub fn AcquisitionReport::unmatched_rule_ids(
self : AcquisitionReport,
) -> Array[String] {
self.unmatched.copy()
}
///|
pub fn AcquisitionReport::to_json(self : AcquisitionReport) -> String {
canonical_json(self.document)
}
///|
pub fn AcquisitionReport::to_json_value(self : AcquisitionReport) -> Json {
copy_json(self.document)
}
///|
/// Apply all matching filename-scoped rules, account for uncovered scans and
/// unmatched rules, and retain complete snapshot/audit/check evidence.
pub fn DatasetIndex::check_acquisition(
self : DatasetIndex,
rules : AcquisitionRules,
) -> AcquisitionReport {
let audit = self.audit_all()
let coverage : Map[String, Array[String]] = Map([])
for path in self.data_paths() {
coverage[path] = []
}
let unmatched : Array[String] = []
let mut violations = 0
let reports = rules.rules.map(rule => {
let paths = self.select_scans(rule.query).paths()
let chosen : Map[String, Bool] = Map([])
for path in paths {
chosen[path] = true
coverage[path].push(rule.id)
}
if paths.is_empty() {
unmatched.push(rule.id)
}
let selected : AuditReport = {
entries: audit.entries.filter(entry => chosen.contains(entry.path)),
}
let checked = selected.check_expectations(rule.expected)
violations += checked.violation_count()
Json::object({
"id": Json::string(rule.id),
"matched_paths": Json::array(paths.map(Json::string)),
"report": checked.to_json_value(),
})
})
let paths = self.data_paths()
let uncovered = paths.filter(path => coverage[path].is_empty())
let errors = audit.error_count()
let outcome : ExpectationDecision = if paths.is_empty() ||
!uncovered.is_empty() ||
!unmatched.is_empty() ||
errors > 0 {
Indeterminate
} else if violations > 0 {
Violations
} else {
Pass
}
let decision = match outcome {
Pass => "pass"
Violations => "violations"
Indeterminate => "indeterminate"
}
{
outcome,
violations,
errors,
uncovered,
unmatched,
document: Json::object({
"decision": Json::string(decision),
"empty_inventory": Json::boolean(paths.is_empty()),
"violation_count": Json::number(violations.to_double()),
"error_count": Json::number(errors.to_double()),
"uncovered_paths": Json::array(uncovered.map(Json::string)),
"unmatched_rule_ids": Json::array(unmatched.map(Json::string)),
"coverage": Json::array(
paths.map(path => {
Json::object({
"data_path": Json::string(path),
"rule_ids": Json::array(coverage[path].map(Json::string)),
})
}),
),
"rule_set": rules.rules_value(),
"rule_reports": Json::array(reports),
"audit": audit.report_value(),
"manifest": self.manifest_value(),
}),
}
}