M12e: contract pass (pair invite kind, admin name in invite links, sync failed flag, rejection actor, device version name, template id, update check result, untouched accounts, D-027 re-stamp, PushEvent in :core:model, snackbar hosts)

Server: E-3 invite_kind; E-12 link ?from=<admin name>, E-4 passes it into kvitt://invite;
X-Kvitt-App-Version-Name stored in devices.app_version_name (migration 0003, schema 3);
OpenAPI and examples follow. Phone: :core:model, :core:fakes, :data, features and :app
updated together; goldens re-recorded for S-20 (version name) and new S-1/S-22 states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-18 15:00:50 +02:00
parent 6159e18198
commit 8c607153c0
96 changed files with 1090 additions and 298 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"status": "ok",
"server_version": "0.1.0",
"schema_version": 2,
"schema_version": 3,
"db_ok": true
}
@@ -3,7 +3,7 @@
"kind": "partner",
"target_user_id": null,
"code": "4N8RVT2KW6ZC",
"link": "https://icehack.asuscomm.com:8443/i/4N8RVT2KW6ZC",
"link": "https://icehack.asuscomm.com:8443/i/4N8RVT2KW6ZC?from=Jacob",
"created_at": "2026-10-17T06:30:02.004Z",
"expires_at": "2026-10-20T06:30:02.004Z"
}
@@ -5,6 +5,7 @@
"user_id": "0192a000-0000-7000-8000-000000000001",
"name": "Galaxy S26",
"app_version_code": 41,
"app_version_name": "1.1",
"created_at": "2026-10-17T06:12:40.512Z",
"last_seen_at": "2026-10-17T09:00:00.000Z",
"revoked_at": null,
@@ -15,6 +16,7 @@
"user_id": "0192a000-0000-7000-8000-000000000001",
"name": "Old phone",
"app_version_code": null,
"app_version_name": null,
"created_at": "2026-10-17T06:20:00.000Z",
"last_seen_at": "2026-10-17T06:25:00.000Z",
"revoked_at": "2026-10-17T06:40:00.000Z",
@@ -25,6 +27,7 @@
"user_id": "0192a000-0000-7000-8000-000000000002",
"name": "Galaxy S26+",
"app_version_code": 41,
"app_version_name": "1.1",
"created_at": "2026-10-17T07:01:15.000Z",
"last_seen_at": "2026-10-17T08:40:12.000Z",
"revoked_at": null,
@@ -1,5 +1,6 @@
{
"token": "kvt_q7Zm0yHc1u2R9vXgB4kLw8sTn3eP6aJdF5oQ1iYbV0c",
"invite_kind": "admin_setup",
"user": {
"id": "0192a000-0000-7000-8000-000000000001",
"role": "admin",
@@ -14,6 +15,7 @@
"user_id": "0192a000-0000-7000-8000-000000000001",
"name": "Galaxy S26",
"app_version_code": 41,
"app_version_name": "1.1",
"created_at": "2026-10-17T06:12:40.512Z",
"last_seen_at": "2026-10-17T09:00:00.000Z",
"revoked_at": null,
+1
View File
@@ -13,6 +13,7 @@
"user_id": "0192a000-0000-7000-8000-000000000001",
"name": "Galaxy S26",
"app_version_code": 41,
"app_version_name": "1.1",
"created_at": "2026-10-17T06:12:40.512Z",
"last_seen_at": "2026-10-17T09:00:00.000Z",
"revoked_at": null,
+55 -3
View File
@@ -36,6 +36,7 @@ paths:
parameters:
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -74,6 +75,7 @@ paths:
parameters:
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -100,6 +102,7 @@ paths:
parameters:
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -148,8 +151,15 @@ paths:
required: true
schema:
type: string
- name: from
in: query
required: false
description: The inviting admin's display name (E-12 link), passed on into the `kvitt://invite` link; an invalid value is ignored.
schema:
type: string
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -185,6 +195,7 @@ paths:
parameters:
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -220,6 +231,7 @@ paths:
type: string
- $ref: '#/components/parameters/ProtocolOptional'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -253,6 +265,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -280,6 +293,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -322,6 +336,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -374,6 +389,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -418,6 +434,7 @@ paths:
type: integer
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -465,6 +482,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -474,7 +492,7 @@ paths:
$ref: '#/components/schemas/CreateInviteRequest'
responses:
'201':
description: The invite with `code` (12 characters, no hyphens) and `link` (`<public_url>/i/<code>`).
description: The invite with `code` (12 characters, no hyphens) and `link` (`<public_url>/i/<code>?from=<admin display name, percent-encoded>`).
headers:
X-Kvitt-Server-Version:
$ref: '#/components/headers/ServerVersion'
@@ -510,6 +528,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -544,6 +563,7 @@ paths:
- $ref: '#/components/parameters/InviteId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'204':
@@ -575,6 +595,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -606,6 +627,7 @@ paths:
- $ref: '#/components/parameters/DeviceId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'204':
@@ -640,6 +662,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -674,6 +697,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -719,6 +743,7 @@ paths:
- $ref: '#/components/parameters/AccountId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -766,6 +791,7 @@ paths:
- $ref: '#/components/parameters/AccountId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -803,6 +829,7 @@ paths:
- $ref: '#/components/parameters/AccountId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -837,6 +864,7 @@ paths:
- $ref: '#/components/parameters/AccountId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -870,6 +898,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -915,6 +944,7 @@ paths:
- $ref: '#/components/parameters/CategoryId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -961,6 +991,7 @@ paths:
- $ref: '#/components/parameters/CategoryId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -998,6 +1029,7 @@ paths:
- $ref: '#/components/parameters/CategoryId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -1032,6 +1064,7 @@ paths:
- $ref: '#/components/parameters/CategoryId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -1065,6 +1098,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -1111,6 +1145,7 @@ paths:
- $ref: '#/components/parameters/TemplateId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -1154,6 +1189,7 @@ paths:
- $ref: '#/components/parameters/TemplateId'
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -1202,6 +1238,7 @@ paths:
default: 500
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
responses:
'200':
@@ -1236,6 +1273,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -1284,6 +1322,7 @@ paths:
parameters:
- $ref: '#/components/parameters/Protocol'
- $ref: '#/components/parameters/AppVersion'
- $ref: '#/components/parameters/AppVersionName'
- $ref: '#/components/parameters/AcceptLanguage'
requestBody:
required: true
@@ -1355,6 +1394,13 @@ components:
description: The app's `versionCode`, sent by the app on every call.
schema:
type: integer
AppVersionName:
name: X-Kvitt-App-Version-Name
in: header
required: false
description: The app's `versionName`, sent by the app on every call; stored per device like `X-Kvitt-App-Version` (1 to 32 characters, anything else ignored).
schema:
type: string
AcceptLanguage:
name: Accept-Language
in: header
@@ -1650,7 +1696,7 @@ components:
Device:
type: object
description: Only in /me, /devices and /invites/accept.
required: [id, user_id, name, app_version_code, created_at, last_seen_at, revoked_at, is_current]
required: [id, user_id, name, app_version_code, app_version_name, created_at, last_seen_at, revoked_at, is_current]
properties:
id:
$ref: '#/components/schemas/Uuid'
@@ -1660,6 +1706,9 @@ components:
type: string
app_version_code:
type: [integer, 'null']
app_version_name:
type: [string, 'null']
description: The app's `versionName` from `X-Kvitt-App-Version-Name`; null until a request carried it.
created_at:
$ref: '#/components/schemas/Timestamp'
last_seen_at:
@@ -2026,11 +2075,14 @@ components:
description: Required for `admin_setup` and `partner` invites (`invalid_request` if missing), ignored for `device`.
AcceptInviteResponse:
type: object
required: [token, user, device, household]
required: [token, invite_kind, user, device, household]
properties:
token:
type: string
description: '`kvt_` + base64url without padding of 32 random bytes (47 characters, SPEC §8.2).'
invite_kind:
$ref: '#/components/schemas/InviteKind'
description: The kind of the accepted invite; the app shows the profile step only after `partner` (AC-2.10).
user:
$ref: '#/components/schemas/User'
device:
@@ -0,0 +1,9 @@
-- Kvitt server schema v3: the app's versionName per device (devices.app_version_name), sent in the
-- X-Kvitt-App-Version-Name header and written like app_version_code (SPEC §14.3.1, §8.2). Do not edit; add a new
-- migration and bump the schema version (SPEC §14.8). Applied on top of 0002_categories_v2.sql.
--
-- Existing devices keep every column and get NULL until their next request that carries the header.
ALTER TABLE devices ADD COLUMN app_version_name TEXT
CHECK (app_version_name IS NULL OR length(app_version_name) BETWEEN 1 AND 32);
UPDATE meta SET value = '3' WHERE key = 'schema_version';