///|
pub(all) struct ProcessingManifest {
document_id : String
input_checksum : String
output_checksum : String
rule_pack_checksum : String
policy_name : String
finding_count : Int
applied_count : Int
generated_at : String
} derive(Debug, Eq)
///|
pub(all) struct RetentionPolicy {
keep_original : Bool
keep_audit : Bool
keep_offsets : Bool
redact_logs : Bool
} derive(Debug, Eq)
///|
pub fn RetentionPolicy::safe_default() -> RetentionPolicy {
{
keep_original: false,
keep_audit: true,
keep_offsets: true,
redact_logs: true,
}
}
///|
pub fn RetentionPolicy::minimal() -> RetentionPolicy {
{
keep_original: false,
keep_audit: false,
keep_offsets: false,
redact_logs: true,
}
}
///|
pub fn retention_summary(policy : RetentionPolicy) -> String {
[
"keep_original=\{policy.keep_original}",
"keep_audit=\{policy.keep_audit}",
"keep_offsets=\{policy.keep_offsets}",
"redact_logs=\{policy.redact_logs}",
].join("\n")
}
///|
pub fn make_manifest(
document_id : String,
input : String,
result : DeidResult,
pack : RulePack,
generated_at : String,
) -> ProcessingManifest {
{
document_id,
input_checksum: stable_hash(input),
output_checksum: stable_hash(result.text),
rule_pack_checksum: pack_checksum(pack),
policy_name: "default",
finding_count: result.findings.length(),
applied_count: result.offsets.length(),
generated_at,
}
}
///|
pub fn manifest_to_json(manifest : ProcessingManifest) -> String {
"{" +
"\"document_id\":\{json_string(manifest.document_id)}," +
"\"input_checksum\":\{json_string(manifest.input_checksum)}," +
"\"output_checksum\":\{json_string(manifest.output_checksum)}," +
"\"rule_pack_checksum\":\{json_string(manifest.rule_pack_checksum)}," +
"\"policy_name\":\{json_string(manifest.policy_name)}," +
"\"finding_count\":\{manifest.finding_count}," +
"\"applied_count\":\{manifest.applied_count}," +
"\"generated_at\":\{json_string(manifest.generated_at)}" +
"}"
}
///|
pub fn manifest_checksum(manifest : ProcessingManifest) -> String {
stable_hash(manifest_to_json(manifest))
}
///|
pub fn manifest_is_consistent(
manifest : ProcessingManifest,
result : DeidResult,
) -> Bool {
manifest.output_checksum == stable_hash(result.text) &&
manifest.finding_count == result.findings.length() &&
manifest.applied_count == result.offsets.length()
}
///|
pub fn redact_with_manifest(
document_id : String,
input : String,
config : RedactionConfig,
generated_at : String,
) -> (DeidResult, ProcessingManifest) raise DeidError {
let result = redact_with_config(input, config)
let manifest = make_manifest(
document_id,
input,
result,
default_rule_pack(),
generated_at,
)
(result, manifest)
}
///|
pub fn safe_audit_export(
report : AuditReport,
retention : RetentionPolicy,
) -> String {
if retention.keep_audit {
if retention.keep_offsets {
audit_to_json(report)
} else {
audit_plain_text(report)
}
} else {
audit_summary(report)
}
}
///|
pub fn safe_finding_export(
findings : Array[Finding],
retention : RetentionPolicy,
) -> String {
if retention.keep_audit {
if retention.keep_offsets {
findings_csv(findings)
} else {
findings
.map(fn(item) {
"\{item.id},\{phi_kind_name(item.kind)},\{item.confidence}"
})
.join("\n")
}
} else {
"findings=\{findings.length()}"
}
}
///|
pub fn safe_text_export(
result : DeidResult,
retention : RetentionPolicy,
) -> String {
if retention.redact_logs {
result.text
} else {
result.text
}
}
///|
pub fn sanitize_log_message(
message : String,
config : RedactionConfig,
) -> String raise DeidError {
redact_with_config(message, config).text
}
///|
pub fn sanitize_log_batch(
messages : Array[String],
config : RedactionConfig,
) -> Array[String] raise DeidError {
messages.map(message => sanitize_log_message(message, config))
}
///|
pub fn data_inventory(input : String, result : DeidResult) -> Map[String, Int] {
let inventory = privacy_class_counts(result.findings)
inventory["documents"] = 1
inventory["input_characters"] = input.char_length()
inventory["output_characters"] = result.text.char_length()
inventory
}
///|
pub fn data_inventory_text(input : String, result : DeidResult) -> String {
let inventory = data_inventory(input, result)
let lines = []
for key, value in inventory {
lines.push("\{key}=\{value}")
}
lines.sort()
lines.join("\n")
}
///|
pub fn audit_for_external_review(input : String, result : DeidResult) -> String {
[
"contract_passes=\{output_contract_passes(input, result)}",
"risk=\{audit_risk_level(result.audit)}",
"inventory:",
data_inventory_text(input, result),
"report_checksum=\{report_checksum(result.audit)}",
].join("\n")
}
///|
pub fn compliance_boundary_notice() -> String {
"Heuristic text detection supports engineering workflows; institutional review, validation, and policy controls remain required."
}
///|
pub fn safe_export_contract(
input : String,
result : DeidResult,
) -> Array[String] {
let issues = output_contract_issues(input, result)
if issues.is_empty() {
["pass"]
} else {
issues
}
}
///|
pub fn manifest_csv(manifests : Array[ProcessingManifest]) -> String {
let lines = [
"document_id,input_checksum,output_checksum,finding_count,applied_count,generated_at",
]
for item in manifests {
lines.push(
[
csv_cell(item.document_id),
item.input_checksum,
item.output_checksum,
"\{item.finding_count}",
"\{item.applied_count}",
csv_cell(item.generated_at),
].join(","),
)
}
lines.join("\n")
}