M1: contracts and vectors (SPEC v0.1, schema 1)

contracts/openapi.yaml (OpenAPI 3.1, E-1..E-33, error shape and the
exhaustive error code enum, device bearer scheme), contracts/schema/
server-v1.sql (SPEC §14.1 verbatim), seven test-vector files per §14.5.5,
80 examples (every JSON body of §14.3.4, every error code, sync pull/push,
every FCM push type), and tools/contracts-check.py (stdlib-only reference
of §9.1, §9.3, §9.4 and §14.5.4 plus shape checks; the M1 gate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 20:57:59 +02:00
parent cab3e935d6
commit c6df2ab8f5
90 changed files with 8931 additions and 0 deletions
@@ -0,0 +1,5 @@
{
"code": "account_not_empty",
"message": "account is referenced by expenses, settlements or templates",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "batch_too_large",
"message": "more than 100 mutations",
"field": "mutations"
}
@@ -0,0 +1,5 @@
{
"code": "cannot_revoke_current_device",
"message": "cannot revoke the calling device",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "category_in_use",
"message": "category is used by expenses or templates",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "category_is_default",
"message": "default categories cannot be deleted",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "cursor_ahead",
"message": "since is greater than the current sequence value",
"field": "since"
}
@@ -0,0 +1,5 @@
{
"code": "device_revoked",
"message": "device is revoked",
"field": null
}
+5
View File
@@ -0,0 +1,5 @@
{
"code": "forbidden",
"message": "role member may not manage invites",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "household_full",
"message": "the household already has two users",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "household_incomplete",
"message": "the partner has not joined yet",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "image_invalid",
"message": "picture cannot be decoded",
"field": null
}
+5
View File
@@ -0,0 +1,5 @@
{
"code": "internal",
"message": "internal error, request id 0192a0ff-0000-7000-8000-000000000001",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "invalid_request",
"message": "missing field `device_name`",
"field": "device_name"
}
@@ -0,0 +1,5 @@
{
"code": "invite_invalid",
"message": "invite code is not valid",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "name_taken",
"message": "an account with this name exists",
"field": "name"
}
@@ -0,0 +1,5 @@
{
"code": "no_accounts",
"message": "no_accounts",
"field": null
}
+5
View File
@@ -0,0 +1,5 @@
{
"code": "not_found",
"message": "no such account",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "not_implemented",
"message": "not implemented",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "payload_too_large",
"message": "body exceeds 1 MiB",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "protocol_unsupported",
"message": "X-Kvitt-Protocol must be 1",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "rate_limited",
"message": "too many requests",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "unauthorized",
"message": "missing or unknown bearer token",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "unavailable",
"message": "database busy",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "unsupported_media_type",
"message": "picture must be image/jpeg, image/png or image/webp",
"field": null
}
@@ -0,0 +1,5 @@
{
"code": "validation_failed",
"message": "amount_ore must be between 1 and 1000000000",
"field": "amount_ore"
}