Compare commits

..
28 Commits
Author SHA1 Message Date
ReaperOfVeriod b89edfafa9 Define shared permissions and version contracts
API checks / checks (push) Canceled after 0s
API dependency and security checks / audit (push) Canceled after 0s
2026-10-25 05:39:57 +01:00
ReaperOfVeriod f1e5148480 add bot invocation contracts 2026-10-25 05:33:11 +01:00
ReaperOfVeriod 1a28d120c2 version server client compatibility contract 2026-10-25 05:32:08 +01:00
ReaperOfVeriod e4e4337852 add collaborator search contract 2026-10-25 05:27:00 +01:00
ReaperOfVeriod 687c6b7cc6 add collaborator contract 2026-10-25 05:24:56 +01:00
ReaperOfVeriod 20086b7776 add API issue and release scaffolding 2026-10-25 05:21:26 +01:00
ReaperOfVeriod 29c63121f4 validate API contracts in CI 2026-10-25 05:20:59 +01:00
ReaperOfVeriod 65018af7a0 add backup inventory contract 2026-10-25 05:20:32 +01:00
ReaperOfVeriod d28b289048 add backup run contract 2026-10-25 05:19:26 +01:00
ReaperOfVeriod 4e8ca67ccc add overlay response contract 2026-10-25 05:12:56 +01:00
ReaperOfVeriod a0d54b426d add bot secrets contract 2026-10-25 05:11:46 +01:00
ReaperOfVeriod 5564a30bf9 add bot command contract 2026-10-25 05:08:12 +01:00
ReaperOfVeriod 2856d04396 add shared chat session contract 2026-10-25 05:06:21 +01:00
ReaperOfVeriod 868e56e5e8 add OAuth scope status contract 2026-10-25 05:05:28 +01:00
ReaperOfVeriod 2e386c1e63 add Guest Star collaboration contract 2026-10-25 05:02:48 +01:00
ReaperOfVeriod 39844e6e51 add chat emote response contract 2026-10-25 05:00:27 +01:00
ReaperOfVeriod 1e58ecd7f0 add server diagnostics contract 2026-10-25 04:54:58 +01:00
ReaperOfVeriod eb2be975c1 add administrator notifications contract 2026-10-25 04:53:29 +01:00
ReaperOfVeriod 8c84e45bf9 feat: add moderation action contract 2026-10-25 04:49:34 +01:00
ReaperOfVeriod c63806b094 feat: add chat send contract 2026-10-25 04:48:01 +01:00
ReaperOfVeriod 06192f4717 feat: add category search contract 2026-10-25 04:46:49 +01:00
ReaperOfVeriod 5f09ad23ee feat: add stream info contracts 2026-10-25 04:45:55 +01:00
ReaperOfVeriod 5b42580245 feat: add activity feed contract 2026-10-25 04:44:15 +01:00
ReaperOfVeriod b22ee5eeab feat: define realtime authentication frame 2026-10-25 04:42:23 +01:00
ReaperOfVeriod c7e57aa015 feat: add readiness contract 2026-10-25 04:40:12 +01:00
ReaperOfVeriod 2760f0234f feat: add Twitch OAuth response contracts 2026-10-25 04:36:50 +01:00
ReaperOfVeriod d5a4027708 feat: define administrator auth contracts 2026-10-25 04:22:32 +01:00
ReaperOfVeriod d555bc0727 feat: define realtime and error contracts 2026-10-25 04:18:32 +01:00
72 changed files with 836 additions and 4 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Bug report
description: Report a reproducible defect.
title: 'bug: '
labels: [bug]
body:
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: List the smallest deterministic reproduction and expected behavior.
validations:
required: true
- type: textarea
id: verification
attributes:
label: Verification context
description: Include repository, branch, version, and relevant checks. Never include secrets.
validations:
required: true
+18
View File
@@ -0,0 +1,18 @@
name: Feature request
description: Propose a change that is consistent with PLAN.md.
title: 'feature: '
labels: [enhancement]
body:
- type: textarea
id: problem
attributes:
label: Problem and proposed behavior
validations:
required: true
- type: checkboxes
id: plan
attributes:
label: Planning
options:
- label: I checked PLAN.md and this does not silently change an existing decision.
required: true
+16 -1
View File
@@ -9,4 +9,19 @@ jobs:
runs-on: debian-latest
steps:
- uses: actions/checkout@v4
- run: test -f README.md && test -f AGENTS.md && test -f docs/versioning.md
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Validate repository documentation
run: test -f README.md && test -f AGENTS.md && test -f docs/versioning.md
- name: Validate JSON contracts and fixtures
run: |
node --input-type=module <<'NODE'
import { readdir, readFile } from 'node:fs/promises';
for (const directory of ['contracts/v1', 'fixtures/v1']) {
for (const name of await readdir(directory)) {
if (!name.endsWith('.json')) continue;
JSON.parse(await readFile(`${directory}/${name}`, 'utf8'));
}
}
NODE
+19
View File
@@ -0,0 +1,19 @@
name: API release check
on:
workflow_dispatch:
push:
tags: ['v*.*.*']
jobs:
release-check:
runs-on: debian-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Validate release metadata
run: test -s CHANGELOG.md && test -s README.md && test -s docs/versioning.md
- name: Validate JSON
run: node --input-type=module -e "import { readdir, readFile } from 'node:fs/promises'; for (const d of ['contracts/v1','fixtures/v1']) for (const f of await readdir(d)) if (f.endsWith('.json')) JSON.parse(await readFile(d + '/' + f, 'utf8'));"
+10
View File
@@ -0,0 +1,10 @@
# Changelog
All notable API contract changes are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Development work is
performed on `dev`; stable release entries are added when the user authorizes a
release.
## [Unreleased]
- Add versioned backup inventory and manual-backup response contracts.
+13 -1
View File
@@ -4,5 +4,17 @@ Shared versioned HTTP and authenticated WebSocket contracts for `trixie-server`
This repository contains schemas, shared enums, error codes, compatibility fixtures, contract tests, and API documentation only.
The first contract slice is under `contracts/v1/` and `fixtures/v1/`. It covers server readiness and health so the client and server can be developed and tested together before Twitch integration.
The first contract slice is under `contracts/v1/` and `fixtures/v1/`. It covers
server readiness, health, errors, and the transport-neutral realtime event
envelope so the client and server can be developed and tested together before
Twitch integration. It also defines the first administrator authentication
request and response envelopes; bearer values in fixtures are synthetic only.
Twitch OAuth link and completion response envelopes are also versioned here;
fixtures contain no real credentials or tokens.
The shared boundary vocabulary includes the ordered permission levels in
`contracts/v1/permissions.json` and the client/server compatibility result in
`contracts/v1/version-negotiation.json`. Server-owned state is delivered as
the authenticated `realtime-event.json` envelope; clients must ignore event
types they do not understand.
+18
View File
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/activity-list-response.json",
"title": "Trixie activity feed response",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["event_id", "event_type", "occurred_at", "payload"],
"properties": {
"event_id": { "type": "string", "minLength": 1 },
"event_type": { "type": "string", "pattern": "^[a-z0-9_.-]+$" },
"occurred_at": { "type": "string", "format": "date-time" },
"payload": { "type": "object" }
}
},
"maxItems": 100
}
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "severity", "title", "message", "seen_at", "resolved_at", "created_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"severity": { "type": "string", "enum": ["info", "warning", "error"] },
"title": { "type": "string" },
"message": { "type": "string" },
"seen_at": { "type": ["string", "null"], "format": "date-time" },
"resolved_at": { "type": ["string", "null"], "format": "date-time" },
"created_at": { "type": "string", "format": "date-time" }
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/auth-login-request.json",
"title": "Trixie Login Request",
"type": "object",
"additionalProperties": false,
"required": ["username", "password"],
"properties": {
"username": { "type": "string", "minLength": 1 },
"password": { "type": "string", "minLength": 1 }
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/auth-login-response.json",
"title": "Trixie Login Response",
"type": "object",
"additionalProperties": false,
"required": ["session_token", "must_replace_credentials"],
"properties": {
"session_token": { "type": "string", "minLength": 1 },
"must_replace_credentials": { "type": "boolean" }
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/auth-session-response.json",
"title": "Trixie Session Response",
"type": "object",
"additionalProperties": false,
"required": ["authenticated"],
"properties": {
"authenticated": { "const": true }
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["filename", "size_bytes", "modified_at"],
"properties": {
"filename": { "type": "string" },
"size_bytes": { "type": "integer", "minimum": 0 },
"modified_at": { "type": ["string", "null"] }
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["status", "filename", "reason"],
"properties": {
"status": { "type": "string", "enum": ["ok", "failed"] },
"filename": { "type": ["string", "null"] },
"reason": { "type": ["string", "null"] }
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/bot-command-invoke-request.json",
"title": "Trixie bot command invocation request",
"type": "object",
"additionalProperties": false,
"required": ["name", "user_id", "permission_level"],
"properties": {
"name": { "type": "string", "minLength": 1, "maxLength": 64 },
"user_id": { "type": "string", "minLength": 1, "maxLength": 64 },
"permission_level": { "enum": ["everyone", "subscriber", "moderator", "broadcaster"] },
"variables": { "type": "object", "additionalProperties": { "type": "string" } }
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/bot-command-invoke-response.json",
"title": "Trixie bot command invocation response",
"type": "object",
"additionalProperties": false,
"required": ["status", "response", "remaining_cooldown_seconds", "error"],
"properties": {
"status": { "enum": ["ok", "cooldown", "not_found", "permission_denied", "error"] },
"response": { "type": ["string", "null"] },
"remaining_cooldown_seconds": { "type": ["integer", "null"], "minimum": 0 },
"error": { "type": ["string", "null"] }
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/bot-commands-response.json",
"title": "Trixie bot command list",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "name", "description", "permission_level", "aliases", "tags", "cooldown_seconds", "cooldown_scope", "response_template", "error_template", "enabled", "failure_count", "disabled_reason"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"name": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
"description": { "type": "string" },
"permission_level": { "enum": ["everyone", "subscriber", "moderator", "broadcaster"] },
"aliases": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"cooldown_seconds": { "type": "integer", "minimum": 0, "maximum": 86400 },
"cooldown_scope": { "enum": ["global", "user", "role"] },
"response_template": { "type": "string" },
"error_template": { "type": ["string", "null"] },
"enabled": { "type": "boolean" },
"failure_count": { "type": "integer", "minimum": 0 },
"disabled_reason": { "type": ["string", "null"] }
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/bot-secrets-response.json",
"title": "Trixie bot secret metadata",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "name", "description", "used_by", "created_at", "updated_at"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"name": { "type": "string", "pattern": "^[A-Za-z0-9_]+$" },
"description": { "type": "string" },
"used_by": { "type": "array", "items": { "type": "string" } },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"maxItems": 50,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["name", "url", "provider"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"url": { "type": "string", "format": "uri", "pattern": "^https://" },
"provider": { "type": "string", "enum": ["twitch", "7tv"] }
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/chat-send-request.json",
"title": "Twitch chat send request",
"type": "object",
"additionalProperties": false,
"required": ["identity", "message"],
"properties": {
"identity": { "enum": ["streamer", "bot"] },
"message": { "type": "string", "minLength": 1, "maxLength": 500 },
"reply_parent_message_id": { "type": "string", "maxLength": 128 }
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/chat-send-response.json",
"title": "Twitch chat send response",
"type": "object",
"additionalProperties": false,
"required": ["message_id", "is_sent"],
"properties": {
"message_id": { "type": "string", "minLength": 1 },
"is_sent": { "type": "boolean" },
"drop_reason": { "type": ["string", "null"] }
}
}
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": ["object", "null"],
"additionalProperties": false,
"required": ["id", "guests"],
"properties": {
"id": { "type": "string" },
"guests": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["user_id"],
"properties": {
"user_id": { "type": "string" },
"user_display_name": { "type": ["string", "null"] },
"slot_id": { "type": ["string", "null"] },
"is_live": { "type": ["boolean", "null"] }
}
}
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "login", "display_name"],
"properties": {
"id": { "type": "string" },
"login": { "type": "string" },
"display_name": { "type": "string" }
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["twitch_user_id", "display_name", "favorite", "last_invited_at"],
"properties": {
"twitch_user_id": { "type": "string" },
"display_name": { "type": "string" },
"favorite": { "type": "boolean" },
"last_invited_at": { "type": ["string", "null"] }
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/error.json",
"title": "Trixie API Error",
"type": "object",
"additionalProperties": false,
"required": ["code", "message", "request_id"],
"properties": {
"code": { "type": "string", "pattern": "^[a-z0-9_]+$" },
"message": { "type": "string", "minLength": 1 },
"request_id": { "type": "string", "minLength": 1 }
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/game-search-response.json",
"title": "Twitch category search response",
"type": "array",
"maxItems": 25,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "name", "box_art_url"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1 },
"box_art_url": { "type": "string", "format": "uri" }
}
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/moderation-action-request.json",
"title": "Twitch moderation action",
"type": "object",
"additionalProperties": false,
"required": ["action", "user_id"],
"properties": {
"action": { "enum": ["ban", "timeout", "unban", "delete_message"] },
"user_id": { "type": "string", "minLength": 1, "maxLength": 64 },
"duration_seconds": { "type": "integer", "minimum": 1, "maximum": 1209600 },
"reason": { "type": "string", "maxLength": 500 },
"message_id": { "type": "string", "maxLength": 128 }
}
}
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/moderation-action-response.json",
"title": "Twitch moderation action response",
"type": "object",
"additionalProperties": false,
"required": ["action", "applied"],
"properties": {
"action": { "type": "string" },
"applied": { "const": true }
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/overlay-response.json",
"title": "Trixie public overlay",
"type": "object",
"additionalProperties": false,
"required": ["slug", "html", "css", "javascript", "enabled", "disabled_design"],
"properties": {
"slug": { "type": "string", "pattern": "^[a-z0-9-]+$" },
"html": { "type": "string" },
"css": { "type": "string" },
"javascript": { "type": "string" },
"enabled": { "type": "boolean" },
"disabled_design": { "type": "string" }
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/permissions.json",
"title": "Trixie Permission Level",
"description": "The ordered permission levels used by chat commands and moderation controls.",
"type": "string",
"enum": ["everyone", "subscriber", "moderator", "broadcaster"]
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/readiness.json",
"title": "Trixie readiness response",
"type": "object",
"additionalProperties": false,
"required": ["status"],
"properties": {
"status": { "enum": ["ready", "database_unavailable"] }
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/realtime-auth-frame.json",
"title": "Trixie realtime authentication frame",
"type": "object",
"additionalProperties": false,
"required": ["frame_type", "token"],
"properties": {
"frame_type": { "const": "authenticate" },
"token": { "type": "string", "minLength": 1 }
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/realtime-event.json",
"title": "Trixie Realtime Event",
"type": "object",
"additionalProperties": false,
"required": ["event_id", "event_type", "occurred_at", "payload"],
"properties": {
"event_id": { "type": "string", "minLength": 1 },
"event_type": { "type": "string", "pattern": "^[a-z0-9_.-]+$" },
"occurred_at": { "type": "string", "format": "date-time" },
"payload": { "type": "object" }
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["status", "database", "twitch_identity_count", "unseen_notification_count", "realtime"],
"properties": {
"status": { "type": "string", "enum": ["healthy"] },
"database": { "type": "string", "enum": ["connected"] },
"twitch_identity_count": { "type": "integer", "minimum": 0 },
"unseen_notification_count": { "type": "integer", "minimum": 0 },
"realtime": { "type": "string", "enum": ["available"] }
}
}
+3 -1
View File
@@ -4,10 +4,12 @@
"title": "Trixie Server Info",
"type": "object",
"additionalProperties": false,
"required": ["api_version", "server_version", "status"],
"required": ["api_version", "server_version", "minimum_client_version", "maximum_client_version", "status"],
"properties": {
"api_version": { "type": "string", "const": "v1" },
"server_version": { "type": "string", "minLength": 1 },
"minimum_client_version": { "type": "string", "minLength": 1 },
"maximum_client_version": { "type": "string", "minLength": 1 },
"status": { "type": "string", "enum": ["ready"] }
}
}
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/shared-chat-session-response.json",
"title": "Twitch Shared Chat session response",
"type": ["object", "null"],
"additionalProperties": false,
"required": ["session_id", "host_broadcaster_id", "participants", "created_at", "updated_at"],
"properties": {
"session_id": { "type": "string", "minLength": 1 },
"host_broadcaster_id": { "type": "string", "minLength": 1 },
"participants": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["broadcaster_id"],
"properties": { "broadcaster_id": { "type": "string", "minLength": 1 } }
}
},
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/stream-info-request.json",
"title": "Twitch stream information update",
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"title": { "type": "string", "maxLength": 140 },
"game_id": { "type": "string", "maxLength": 64 },
"language": { "type": "string", "maxLength": 10 },
"tags": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 25 } },
"content_classification_labels": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 64 } }
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/stream-info-response.json",
"title": "Twitch stream information",
"type": "object",
"additionalProperties": false,
"required": ["broadcaster_id", "broadcaster_name", "game_id", "game_name", "title", "language", "tags", "content_classification_labels"],
"properties": {
"broadcaster_id": { "type": "string", "minLength": 1 },
"broadcaster_name": { "type": "string", "minLength": 1 },
"game_id": { "type": "string" },
"game_name": { "type": "string" },
"title": { "type": "string" },
"language": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } },
"content_classification_labels": { "type": "array", "items": { "type": "string" } }
}
}
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-link-response.json",
"title": "Twitch OAuth authorization link response",
"type": "object",
"additionalProperties": false,
"required": ["identity", "scopes", "authorization_url", "expires_in_seconds"],
"properties": {
"identity": { "enum": ["streamer", "bot", "moderator"] },
"scopes": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"minItems": 1,
"uniqueItems": true
},
"authorization_url": {
"type": "string",
"format": "uri",
"pattern": "^https://id\\.twitch\\.tv/oauth2/authorize\\?"
},
"expires_in_seconds": { "type": "integer", "const": 600 }
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-result.json",
"title": "Twitch OAuth completion response",
"type": "object",
"additionalProperties": false,
"required": ["identity", "twitch_user_id", "display_name"],
"properties": {
"identity": { "enum": ["streamer", "bot", "moderator"] },
"twitch_user_id": { "type": "string", "minLength": 1 },
"display_name": { "type": "string", "minLength": 1 }
}
}
@@ -0,0 +1,16 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-status-response.json",
"title": "Twitch OAuth scope status response",
"type": "object",
"additionalProperties": false,
"required": ["identity", "connected", "required_scopes", "granted_scopes", "missing_scopes", "reauthorization_required"],
"properties": {
"identity": { "enum": ["streamer", "bot", "moderator"] },
"connected": { "type": "boolean" },
"required_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"granted_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"missing_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"reauthorization_required": { "type": "boolean" }
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://trixie.invalid/contracts/v1/version-negotiation.json",
"title": "Trixie Version Negotiation",
"type": "object",
"additionalProperties": false,
"required": ["api_version", "client_version", "server_version", "compatible"],
"properties": {
"api_version": { "type": "string", "const": "v1" },
"client_version": { "type": "string", "minLength": 1 },
"server_version": { "type": "string", "minLength": 1 },
"compatible": { "type": "boolean" },
"reason": { "type": "string", "minLength": 1 }
}
}
+15 -1
View File
@@ -1,4 +1,18 @@
# Contract Testing
Fixtures cover HTTP responses, authenticated WebSocket events, errors, permissions, version negotiation, reconnects, and server-owned state transitions. Fixtures are deterministic, synthetic, and secret-free.
Fixtures cover HTTP responses, authenticated WebSocket events, errors, permissions,
version negotiation, reconnects, and server-owned state transitions. Fixtures are
deterministic, synthetic, and secret-free. Every new contract must include at
least one valid fixture and a negative case where validation rules are important.
The current realtime envelope is deliberately transport-neutral: the server may
deliver it over an authenticated WebSocket or a future compatible transport, but
the event identity and payload rules remain the same. `event_id` is the stable
deduplication key for at-least-once delivery.
Browser clients authenticate the WebSocket with the first text frame described
by `realtime-auth-frame.json`. Session tokens are sent in that frame rather
than in a URL query parameter. The server closes the connection when the frame
is missing, malformed, or invalid, and sends `connection.ready` only after
successful validation.
+9
View File
@@ -5,4 +5,13 @@
- Clients must tolerate unknown fields and events.
- Breaking changes require a new major version, fixtures, migration notes, and coordinated releases.
- Each release records minimum compatible client and server versions.
- `GET /api/v1/server/info` publishes `minimum_client_version` and
`maximum_client_version`; clients must use these fields to detect an
incompatible pairing before enabling authenticated controls.
- Permission values are shared through `contracts/v1/permissions.json`.
They are ordered from least to most privileged: `everyone`, `subscriber`,
`moderator`, and `broadcaster`.
- The normalized result of a compatibility check is described by
`contracts/v1/version-negotiation.json`; an incompatible result includes a
human-readable `reason` while a compatible result may omit it.
+8
View File
@@ -0,0 +1,8 @@
[
{
"event_id": "synthetic-event-1",
"event_type": "channel.follow",
"occurred_at": "2026-10-25T12:00:00Z",
"payload": { "user_name": "SyntheticViewer" }
}
]
@@ -0,0 +1,11 @@
[
{
"id": "00000000-0000-0000-0000-000000000001",
"severity": "warning",
"title": "IP permanently rate-limited",
"message": "A source IP reached the login failure limit and requires review.",
"seen_at": null,
"resolved_at": null,
"created_at": "2026-10-25T12:00:00Z"
}
]
+4
View File
@@ -0,0 +1,4 @@
{
"session_token": "fixture-session-token-never-use",
"must_replace_credentials": true
}
@@ -0,0 +1,3 @@
{
"authenticated": true
}
+7
View File
@@ -0,0 +1,7 @@
[
{
"filename": "trixie-20261025T030000Z.dump",
"size_bytes": 4096,
"modified_at": "2026-10-25T03:00:01Z"
}
]
+5
View File
@@ -0,0 +1,5 @@
{
"status": "ok",
"filename": "trixie-20261025T030000Z.dump",
"reason": null
}
@@ -0,0 +1,6 @@
{
"status": "ok",
"response": "Hello Alice",
"remaining_cooldown_seconds": null,
"error": null
}
+17
View File
@@ -0,0 +1,17 @@
[
{
"id": "00000000-0000-4000-8000-000000000001",
"name": "uptime",
"description": "Show uptime",
"permission_level": "everyone",
"aliases": [],
"tags": ["utility"],
"cooldown_seconds": 2,
"cooldown_scope": "user",
"response_template": "Online",
"error_template": null,
"enabled": true,
"failure_count": 0,
"disabled_reason": null
}
]
+10
View File
@@ -0,0 +1,10 @@
[
{
"id": "00000000-0000-4000-8000-000000000002",
"name": "API_Key",
"description": "External API key",
"used_by": [],
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
]
+12
View File
@@ -0,0 +1,12 @@
[
{
"name": "Kappa",
"url": "https://static-cdn.jtvnw.net/emoticons/v2/25/static/dark/2.0",
"provider": "twitch"
},
{
"name": "catJAM",
"url": "https://cdn.7tv.app/emote/60ae8f6f625f5a2a4d4e2b6f/2x.webp",
"provider": "7tv"
}
]
+5
View File
@@ -0,0 +1,5 @@
{
"message_id": "synthetic-message-id",
"is_sent": true,
"drop_reason": null
}
@@ -0,0 +1,4 @@
{
"id": "guest-star-session-1",
"guests": []
}
@@ -0,0 +1,7 @@
[
{
"id": "123456",
"login": "example_streamer",
"display_name": "ExampleStreamer"
}
]
+8
View File
@@ -0,0 +1,8 @@
[
{
"twitch_user_id": "123456",
"display_name": "ExampleStreamer",
"favorite": true,
"last_invited_at": "2026-10-25T12:00:00Z"
}
]
+5
View File
@@ -0,0 +1,5 @@
{
"code": "unauthorized",
"message": "Authentication is required.",
"request_id": "req_fixture_unauthorized"
}
+7
View File
@@ -0,0 +1,7 @@
[
{
"id": "123",
"name": "Synthetic Game",
"box_art_url": "https://static-cdn.jtvnw.net/ttv-boxart/123-52x72.jpg"
}
]
@@ -0,0 +1,4 @@
{
"action": "timeout",
"applied": true
}
+8
View File
@@ -0,0 +1,8 @@
{
"slug": "followers",
"html": "<div id=\"alert\"></div>",
"css": "#alert { color: white; }",
"javascript": "",
"enabled": true,
"disabled_design": "transparent"
}
+3
View File
@@ -0,0 +1,3 @@
{
"status": "ready"
}
+4
View File
@@ -0,0 +1,4 @@
{
"frame_type": "authenticate",
"token": "synthetic-session-token"
}
+9
View File
@@ -0,0 +1,9 @@
{
"event_id": "evt_fixture_activity_001",
"event_type": "activity.follow",
"occurred_at": "2026-01-01T00:00:00Z",
"payload": {
"username": "fixture_viewer",
"display_name": "Fixture Viewer"
}
}
@@ -0,0 +1,7 @@
{
"status": "healthy",
"database": "connected",
"twitch_identity_count": 2,
"unseen_notification_count": 0,
"realtime": "available"
}
+2
View File
@@ -1,5 +1,7 @@
{
"api_version": "v1",
"server_version": "0.1.0-dev",
"minimum_client_version": "0.1.0",
"maximum_client_version": "0.x",
"status": "ready"
}
@@ -0,0 +1,7 @@
{
"session_id": "shared-session-1",
"host_broadcaster_id": "1234",
"participants": [{ "broadcaster_id": "1234" }, { "broadcaster_id": "5678" }],
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z"
}
+10
View File
@@ -0,0 +1,10 @@
{
"broadcaster_id": "synthetic-broadcaster-id",
"broadcaster_name": "SyntheticStreamer",
"game_id": "123",
"game_name": "Synthetic Game",
"title": "A synthetic stream",
"language": "en",
"tags": ["Synthetic"],
"content_classification_labels": []
}
@@ -0,0 +1,6 @@
{
"identity": "bot",
"scopes": ["channel:bot", "user:read:chat", "user:write:chat"],
"authorization_url": "https://id.twitch.tv/oauth2/authorize?client_id=public-example-client&redirect_uri=http%3A%2F%2Flocalhost%3A8787%2Fapi%2Fv1%2Ftwitch%2Foauth%2Fcallback&response_type=code&scope=channel%3Abot+user%3Aread%3Achat+user%3Awrite%3Achat&state=synthetic-state",
"expires_in_seconds": 600
}
@@ -0,0 +1,5 @@
{
"identity": "streamer",
"twitch_user_id": "synthetic-twitch-user-id",
"display_name": "SyntheticStreamer"
}
@@ -0,0 +1,8 @@
{
"identity": "streamer",
"connected": false,
"required_scopes": ["user:read:chat"],
"granted_scopes": [],
"missing_scopes": ["user:read:chat"],
"reauthorization_required": true
}
@@ -0,0 +1,6 @@
{
"api_version": "v1",
"client_version": "0.1.0",
"server_version": "0.1.0",
"compatible": true
}