2026-10-25 15:28:12 +00:00
|
|
|
package proofedmesh.transformations.expense_test
|
|
|
|
|
|
|
|
|
|
import data.proofedmesh.transformations.expense
|
|
|
|
|
import rego.v1
|
|
|
|
|
|
2026-10-25 16:19:37 +00:00
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# v1.0.0 — flat single-project (unchanged behaviour)
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
2026-10-25 15:28:12 +00:00
|
|
|
canonical_payload := {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sink_payload := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
test_allow_map_to_sink if {
|
|
|
|
|
expense.allow with input as {
|
|
|
|
|
"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"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": sink_payload,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_unknown_agent if {
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-rogue",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.0.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.0.0"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": sink_payload,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_schema_mismatch if {
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"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": "2.0.0"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": sink_payload,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_total_amount_change if {
|
|
|
|
|
tampered := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "999.99",
|
|
|
|
|
}}
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"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"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": tampered,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_currency_change if {
|
|
|
|
|
tampered := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "USD",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
}}
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"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"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": tampered,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_employee_change if {
|
|
|
|
|
tampered := {"expense": {
|
|
|
|
|
"employee_id": "EMP-99999",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
}}
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"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"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": tampered,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_posting_agent_cannot_map if {
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-sink-posting",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.0.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.0.0"},
|
|
|
|
|
"input_payload": canonical_payload,
|
|
|
|
|
"output_payload": sink_payload,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-25 16:19:37 +00:00
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
# v1.1.0 — per-line allocations
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
canonical_v11 := {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sink_v11_two_items_split := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
"items": [
|
|
|
|
|
{
|
|
|
|
|
"amount": "328.50",
|
|
|
|
|
"allocations": [
|
|
|
|
|
{"amount": "197.10"},
|
|
|
|
|
{"amount": "131.40"},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"amount": "100.00",
|
|
|
|
|
"allocations": [
|
|
|
|
|
{"amount": "70.00"},
|
|
|
|
|
{"amount": "30.00"},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
test_allow_v110_map_to_sink_split if {
|
|
|
|
|
expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-sink-mapping",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.1.0"},
|
|
|
|
|
"input_payload": canonical_v11,
|
|
|
|
|
"output_payload": sink_v11_two_items_split,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_deny_v110_allocation_sum_mismatch if {
|
|
|
|
|
bad_sink := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
"items": [
|
|
|
|
|
{
|
|
|
|
|
"amount": "328.50",
|
|
|
|
|
"allocations": [
|
|
|
|
|
{"amount": "197.10"},
|
|
|
|
|
{"amount": "131.50"}, # 328.60, nicht 328.50
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"amount": "100.00",
|
|
|
|
|
"allocations": [
|
|
|
|
|
{"amount": "70.00"},
|
|
|
|
|
{"amount": "30.00"},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}}
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-sink-mapping",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.1.0"},
|
|
|
|
|
"input_payload": canonical_v11,
|
|
|
|
|
"output_payload": bad_sink,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_allow_v110_single_allocation if {
|
|
|
|
|
one := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "428.50",
|
|
|
|
|
"items": [
|
|
|
|
|
{"amount": "328.50", "allocations": [{"amount": "328.50"}]},
|
|
|
|
|
{"amount": "100.00", "allocations": [{"amount": "100.00"}]},
|
|
|
|
|
],
|
|
|
|
|
}}
|
|
|
|
|
expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-sink-mapping",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.1.0"},
|
|
|
|
|
"input_payload": canonical_v11,
|
|
|
|
|
"output_payload": one,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_v110_still_enforces_total_currency_employee if {
|
|
|
|
|
tampered_total := {"expense": {
|
|
|
|
|
"employee_id": "EMP-12345",
|
|
|
|
|
"currency": "EUR",
|
|
|
|
|
"total_amount": "999.99",
|
|
|
|
|
"items": [{"amount": "328.50", "allocations": [{"amount": "328.50"}]}],
|
|
|
|
|
}}
|
|
|
|
|
not expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-sink-mapping",
|
|
|
|
|
"operation": "expense.map_to_sink",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"output_schema": {"name": "sink.db.expense", "version": "1.1.0"},
|
|
|
|
|
"input_payload": canonical_v11,
|
|
|
|
|
"output_payload": tampered_total,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test_allow_v110_normalize if {
|
|
|
|
|
# normalize keeps canonical v1.1.0 shape, no sink allocation check applies.
|
|
|
|
|
expense.allow with input as {
|
|
|
|
|
"agent_id": "agent-travel",
|
|
|
|
|
"operation": "expense.normalize",
|
|
|
|
|
"input_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"output_schema": {"name": "expense.canonical", "version": "1.1.0"},
|
|
|
|
|
"input_payload": canonical_v11,
|
|
|
|
|
"output_payload": {"expense": canonical_v11},
|
|
|
|
|
}
|
|
|
|
|
}
|