# METADATA # title: Transformation policies for expense workflows # description: | # Governs which agents may perform which transformations on expense # artifacts, and enforces the invariants that must survive mapping. # authors: # - CTO Agent # related_resources: # - ref: docs/architecture/v1.1.md#user-content-6-berechtigungssteuerung-mit-open-policy-agent # - ref: schemas/expense/canonical/v1.0.0.json # - ref: schemas/sink/db/expense/v1.0.0.json package proofedmesh.transformations.expense import rego.v1 default allow := false # Allow only known agent + operation combinations. allow if { some rule in agent_allowed rule.agent_id == input.agent_id rule.operation == input.operation schemas_match(rule) invariants_hold } # Rule table — the authoritative map of what each agent may do. agent_allowed := [ { "agent_id": "agent-travel", "operation": "expense.extract_receipts", "input_schema": {"name": "receipt.raw", "version": "1.0.0"}, "output_schema": {"name": "expense.canonical", "version": "1.0.0"}, }, { "agent_id": "agent-travel", "operation": "expense.normalize", "input_schema": {"name": "expense.canonical", "version": "1.0.0"}, "output_schema": {"name": "expense.canonical", "version": "1.0.0"}, }, { "agent_id": "agent-sink-mapping", "operation": "expense.map_to_sink", "input_schema": {"name": "expense.canonical", "version": "1.0.0"}, "output_schema": {"name": "sink.db.expense", "version": "1.0.0"}, }, ] schemas_match(rule) if { input.input_schema.name == rule.input_schema.name input.input_schema.version == rule.input_schema.version input.output_schema.name == rule.output_schema.name input.output_schema.version == rule.output_schema.version } # Business invariants that no transformation may violate without a new approval. invariants_hold if { total_amount_preserved currency_preserved employee_id_preserved } # total_amount from the canonical input must appear unchanged in the sink output. # Only enforced when both sides carry the fields. total_amount_preserved if { not input.input_payload.total_amount } total_amount_preserved if { input.input_payload.total_amount == input.output_payload.expense.total_amount } currency_preserved if { not input.input_payload.currency } currency_preserved if { input.input_payload.currency == input.output_payload.expense.currency } employee_id_preserved if { not input.input_payload.employee_id } employee_id_preserved if { input.input_payload.employee_id == input.output_payload.expense.employee_id } # Human-readable denial reasons for debugging and audit. deny_reason contains msg if { not any_rule_matches msg := sprintf("no allow rule for agent=%s operation=%s", [input.agent_id, input.operation]) } deny_reason contains msg if { any_rule_matches not total_amount_preserved msg := "total_amount changed by transformation" } deny_reason contains msg if { any_rule_matches not currency_preserved msg := "currency changed by transformation" } deny_reason contains msg if { any_rule_matches not employee_id_preserved msg := "employee_id changed by transformation" } any_rule_matches if { some rule in agent_allowed rule.agent_id == input.agent_id rule.operation == input.operation schemas_match(rule) }