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 runs-on: debian-latest
steps: steps:
- uses: actions/checkout@v4 - 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. 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", "title": "Trixie Server Info",
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": ["api_version", "server_version", "status"], "required": ["api_version", "server_version", "minimum_client_version", "maximum_client_version", "status"],
"properties": { "properties": {
"api_version": { "type": "string", "const": "v1" }, "api_version": { "type": "string", "const": "v1" },
"server_version": { "type": "string", "minLength": 1 }, "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"] } "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 # 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. - Clients must tolerate unknown fields and events.
- Breaking changes require a new major version, fixtures, migration notes, and coordinated releases. - Breaking changes require a new major version, fixtures, migration notes, and coordinated releases.
- Each release records minimum compatible client and server versions. - 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", "api_version": "v1",
"server_version": "0.1.0-dev", "server_version": "0.1.0-dev",
"minimum_client_version": "0.1.0",
"maximum_client_version": "0.x",
"status": "ready" "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
}