ProofedMesh/schemas/sink/db/expense/v1.1.0.json
CTO Agent 81e5f334b5
Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 12s
validate / JSON Schema (ajv) (push) Has been cancelled
schemas: add expense.canonical v1.1.0 and sink.db.expense v1.1.0 — per-line project allocations (percentage or absolute)
2026-10-25 16:19:21 +00:00

92 lines
4.6 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://basicstack.de/schemas/sink/db/expense/v1.1.0.json",
"title": "Sink Payload: sink:expense-db-app, action expense.post v1.1.0",
"description": "Senken-Payload für die eigene DB-App mit Zeilen-Allokationen. Agent-sink-mapping löst alle percentage-Modi aus expense.canonical v1.1.0 in absolute Beträge auf; die Senke erhält ausschließlich absolute amounts. Jedes Item trägt sein eigenes allocations[]-Array (Pflicht, mindestens 1 Entry). Die DB-App persistiert diese als expense_item_allocations-Zeilen (siehe docs/architecture/v1.1.md Abschnitt 7.2).",
"type": "object",
"additionalProperties": false,
"required": ["schema", "workflow_id", "approval_id", "decision_id", "verification_ref", "canonical_payload_hash", "expense"],
"properties": {
"schema": {
"type": "object",
"additionalProperties": false,
"required": ["name", "version"],
"properties": {
"name": { "const": "sink.db.expense" },
"version": { "const": "1.1.0" }
}
},
"workflow_id": { "type": "string", "pattern": "^WF-[0-9]{4}-[0-9]{4,}$" },
"approval_id": { "type": "string", "pattern": "^APR-[0-9]{4}-[0-9]{4,}$" },
"decision_id": { "type": "string", "pattern": "^DEC-[0-9]{4}-[0-9]{4,}$" },
"verification_ref": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"canonical_payload_hash": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
"expense": {
"type": "object",
"additionalProperties": false,
"required": ["employee_id", "currency", "total_amount", "trip", "items"],
"properties": {
"employee_id": { "type": "string", "pattern": "^EMP-[0-9]+$" },
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
"total_amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
"trip": {
"type": "object",
"additionalProperties": false,
"required": ["start_date", "end_date", "destination"],
"properties": {
"start_date": { "type": "string", "format": "date" },
"end_date": { "type": "string", "format": "date" },
"destination": { "type": "string", "maxLength": 200 },
"purpose": { "type": "string", "maxLength": 500 }
}
},
"items": {
"type": "array",
"minItems": 1,
"maxItems": 200,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["type", "amount", "incurred_on", "allocations"],
"properties": {
"type": {
"type": "string",
"enum": ["HOTEL", "MEALS", "TRANSPORT_FLIGHT", "TRANSPORT_RAIL", "TRANSPORT_CAR", "PARKING", "COMMUNICATION", "CONFERENCE_FEE", "OTHER"]
},
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
"incurred_on": { "type": "string", "format": "date" },
"description": { "type": "string", "maxLength": 500 },
"receipt_ref": { "type": "string", "pattern": "^pii://[A-Za-z0-9/._-]+$" },
"allocations": {
"description": "Immer gesetzt in der Senken-Fassung. Mindestens 1 Entry. Alle amounts sind absolut (percentage ist informativ, falls bekannt). Summe aller amount == item.amount.",
"type": "array",
"minItems": 1,
"maxItems": 50,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["project_code", "cost_center", "gl_account", "amount", "source_attestation_ref"],
"properties": {
"project_code": { "type": "string", "pattern": "^P-[0-9]+$" },
"cost_center": { "type": "string", "pattern": "^CC-[0-9]+$" },
"gl_account": { "type": "string", "pattern": "^[0-9]{4,6}$" },
"amount": { "type": "string", "pattern": "^-?[0-9]+\\.[0-9]{2}$" },
"percentage": {
"type": "string",
"pattern": "^(100\\.00|[0-9]{1,2}\\.[0-9]{2})$",
"description": "Optional. Nur gesetzt, wenn canonical mode=percentage war — als Herkunftsnachweis."
},
"source_attestation_ref": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$"
}
}
}
}
}
}
}
}
}
}
}