Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54141e9ccb | ||
|
|
2957524af9 | ||
|
|
28478cf5fe | ||
|
|
fb7f13f0ab | ||
|
|
67378bd193 | ||
|
|
99a1e3e07d | ||
|
|
ceef6b11b2 | ||
|
|
0400b12566 | ||
|
|
4efa617e25 | ||
|
|
fece516828 | ||
|
|
ee7b4c5404 | ||
|
|
c96e61e526 | ||
|
|
97ae23e1f2 | ||
|
|
bb9b00295f | ||
|
|
b89edfafa9 | ||
|
|
f1e5148480 | ||
|
|
1a28d120c2 | ||
|
|
e4e4337852 | ||
|
|
687c6b7cc6 | ||
|
|
20086b7776 | ||
|
|
29c63121f4 | ||
|
|
65018af7a0 | ||
|
|
d28b289048 | ||
|
|
4e8ca67ccc | ||
|
|
a0d54b426d | ||
|
|
5564a30bf9 | ||
|
|
2856d04396 | ||
|
|
868e56e5e8 | ||
|
|
2e386c1e63 | ||
|
|
39844e6e51 | ||
|
|
1e58ecd7f0 | ||
|
|
eb2be975c1 | ||
|
|
8c84e45bf9 | ||
|
|
c63806b094 | ||
|
|
06192f4717 | ||
|
|
5f09ad23ee | ||
|
|
5b42580245 | ||
|
|
b22ee5eeab | ||
|
|
c7e57aa015 | ||
|
|
2760f0234f | ||
|
|
d5a4027708 | ||
|
|
d555bc0727 | ||
|
|
90335ce1bd | ||
|
|
94b8a57010 | ||
|
|
608a8e558e |
@@ -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
|
||||
@@ -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
|
||||
@@ -9,4 +9,26 @@ 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
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Validate restored-session schema and negative cases
|
||||
run: |
|
||||
python -m pip install jsonschema==4.26.0
|
||||
python tests/session_contract.py
|
||||
- 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
|
||||
|
||||
@@ -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'));"
|
||||
@@ -0,0 +1,16 @@
|
||||
name: API dependency and security checks
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: debian-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Audit JavaScript dependencies when a lockfile exists
|
||||
run: |
|
||||
if test -f package-lock.json; then
|
||||
npm audit --audit-level=high
|
||||
fi
|
||||
@@ -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.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ReaperOfVeriod
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -4,3 +4,53 @@ 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 API repository is the boundary between the server authority and the Linux
|
||||
client presentation layer. It contains no Twitch credentials, database logic,
|
||||
or UI behavior.
|
||||
|
||||
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.
|
||||
|
||||
`GET /api/v1/auth/session` returns `authenticated: true` and the current
|
||||
`must_replace_credentials` flag from the administrator account. Restoring a
|
||||
session must not bypass first-run setup. Older servers omit this flag; clients
|
||||
must not interpret an absent flag as completed credential replacement.
|
||||
|
||||
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.
|
||||
|
||||
Guest Star channel settings are described by
|
||||
`contracts/v1/guest-star-channel-settings-response.json`. The browser-source
|
||||
token is a server-to-authenticated-client value and fixtures use a synthetic
|
||||
placeholder only.
|
||||
|
||||
The Twitch-authoritative collaboration capability probe is described by
|
||||
`collaboration-capabilities-response.json`; unavailable beta surfaces include
|
||||
a machine-readable reason without exposing Twitch tokens.
|
||||
|
||||
Destructive backup recovery uses the explicit `backup-restore-request.json`
|
||||
and `backup-restore-response.json` contracts; test fixtures contain no real
|
||||
database paths or credentials.
|
||||
|
||||
The authenticated WebAssembly plugin lifecycle uses the manifest, install,
|
||||
installed-response, and plugin-secret-binding-request contracts. Fixtures
|
||||
contain only synthetic package metadata and no executable module or secret.
|
||||
|
||||
Command simulation uses `bot-command-simulate-request.json` and
|
||||
`bot-command-simulate-response.json`. A sample response is the default and
|
||||
real network requests require an explicit `use_real_request` flag; fixtures
|
||||
contain only local synthetic data.
|
||||
|
||||
Named-user command rules use `bot-command-user-rule.json`; fixtures contain
|
||||
only synthetic Twitch IDs and literal response templates.
|
||||
|
||||
Typed command arguments use `bot-command-argument-definition.json` and remain
|
||||
literal validated data.
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$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", "must_replace_credentials"],
|
||||
"properties": {
|
||||
"authenticated": { "const": true },
|
||||
"must_replace_credentials": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/backup-restore-request.json",
|
||||
"title": "Backup restore request",
|
||||
"type": "object",
|
||||
"required": ["filename", "confirmation"],
|
||||
"properties": {
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"pattern": "^trixie-[A-Za-z0-9T_.-]+\\.dump$",
|
||||
"maxLength": 128
|
||||
},
|
||||
"confirmation": { "const": "RESTORE" },
|
||||
"emergency_override": { "type": "boolean", "default": false }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/backup-restore-response.json",
|
||||
"title": "Backup restore response",
|
||||
"type": "object",
|
||||
"required": ["status", "filename", "reason"],
|
||||
"properties": {
|
||||
"status": { "type": "string", "enum": ["ok", "failed"] },
|
||||
"filename": { "type": "string" },
|
||||
"reason": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -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,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/bot-command-argument-definition.json",
|
||||
"title": "Trixie typed bot command argument",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name", "kind", "required"],
|
||||
"properties": {
|
||||
"name": { "type": "string", "pattern": "^[A-Za-z0-9_]{1,64}$" },
|
||||
"kind": { "enum": ["text", "number", "choice", "username", "duration"] },
|
||||
"required": { "type": "boolean" },
|
||||
"default": { "type": ["string", "null"], "maxLength": 200 },
|
||||
"minimum": { "type": ["integer", "null"] },
|
||||
"maximum": { "type": ["integer", "null"] },
|
||||
"choices": { "type": "array", "maxItems": 64, "items": { "type": "string", "minLength": 1, "maxLength": 200 } }
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/bot-command-simulate-request.json",
|
||||
"title": "Trixie bot command simulation request",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["template"],
|
||||
"properties": {
|
||||
"template": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
||||
"variables": { "type": "object", "additionalProperties": { "type": "string", "maxLength": 2000 } },
|
||||
"web_request": { "type": ["object", "null"] },
|
||||
"sample_response": {},
|
||||
"use_real_request": { "type": "boolean", "default": false },
|
||||
"response_fields": {
|
||||
"type": "array",
|
||||
"maxItems": 128,
|
||||
"items": {
|
||||
"type": "array",
|
||||
"prefixItems": [
|
||||
{ "type": "string", "minLength": 1, "maxLength": 64 },
|
||||
{ "type": "string", "minLength": 1, "maxLength": 256 }
|
||||
],
|
||||
"items": false,
|
||||
"minItems": 2,
|
||||
"maxItems": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/bot-command-simulate-response.json",
|
||||
"title": "Trixie bot command simulation response",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["status", "extracted_variables"],
|
||||
"properties": {
|
||||
"status": { "const": "ok" },
|
||||
"rendered": { "type": ["string", "null"], "maxLength": 2000 },
|
||||
"extracted_variables": { "type": "object", "additionalProperties": { "type": "string" } },
|
||||
"error": { "type": ["string", "null"], "maxLength": 500 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/bot-command-user-rule.json",
|
||||
"title": "Trixie named-user command rule",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["user_id"],
|
||||
"properties": {
|
||||
"user_id": { "type": "string", "pattern": "^[0-9]{1,64}$" },
|
||||
"response_template": { "type": ["string", "null"], "minLength": 1, "maxLength": 2000 }
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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,14 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/collaboration-capabilities-response.json",
|
||||
"title": "Collaboration capabilities response",
|
||||
"type": "object",
|
||||
"required": ["guest_star_available", "shared_chat_available"],
|
||||
"properties": {
|
||||
"guest_star_available": { "type": "boolean" },
|
||||
"guest_star_reason": { "type": ["string", "null"] },
|
||||
"shared_chat_available": { "type": "boolean" },
|
||||
"shared_chat_reason": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$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"] },
|
||||
"user_login": { "type": ["string", "null"] },
|
||||
"slot_id": { "type": ["string", "null"] },
|
||||
"is_live": { "type": ["boolean", "null"] },
|
||||
"volume": { "type": ["integer", "null"], "minimum": 0, "maximum": 100 },
|
||||
"assigned_at": { "type": ["string", "null"], "format": "date-time" },
|
||||
"audio_settings": { "$ref": "#/$defs/media_settings" },
|
||||
"video_settings": { "$ref": "#/$defs/media_settings" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"media_settings": {
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"is_host_enabled": { "type": ["boolean", "null"] },
|
||||
"is_guest_enabled": { "type": ["boolean", "null"] },
|
||||
"is_available": { "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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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,24 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/guest-star-channel-settings-response.json",
|
||||
"title": "Guest Star channel settings response",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"is_moderator_send_live_enabled",
|
||||
"slot_count",
|
||||
"is_browser_source_audio_enabled",
|
||||
"group_layout",
|
||||
"browser_source_token"
|
||||
],
|
||||
"properties": {
|
||||
"is_moderator_send_live_enabled": { "type": "boolean" },
|
||||
"slot_count": { "type": "integer", "minimum": 1, "maximum": 6 },
|
||||
"is_browser_source_audio_enabled": { "type": "boolean" },
|
||||
"group_layout": {
|
||||
"type": "string",
|
||||
"enum": ["TILED_LAYOUT", "SCREENSHARE_LAYOUT", "HORIZONTAL_LAYOUT", "VERTICAL_LAYOUT"]
|
||||
},
|
||||
"browser_source_token": { "type": "string", "minLength": 1 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/guest-star-invites-response.json",
|
||||
"title": "Guest Star invites response",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["user_id", "invited_at", "status", "is_video_enabled", "is_audio_enabled"],
|
||||
"properties": {
|
||||
"user_id": { "type": "string", "minLength": 1 },
|
||||
"invited_at": { "type": "string", "format": "date-time" },
|
||||
"status": { "type": "string", "enum": ["INVITED", "ACCEPTED", "READY"] },
|
||||
"is_video_enabled": { "type": "boolean" },
|
||||
"is_audio_enabled": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/health.json",
|
||||
"title": "Trixie Health",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["status"],
|
||||
"properties": {
|
||||
"status": { "type": "string", "enum": ["ok"] }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$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", "alert_group_window_ms", "alert_group_size", "alert_display_ms"],
|
||||
"properties": {
|
||||
"slug": { "type": "string", "pattern": "^[a-z0-9-]+$" },
|
||||
"html": { "type": "string" },
|
||||
"css": { "type": "string" },
|
||||
"javascript": { "type": "string" },
|
||||
"enabled": { "type": "boolean" },
|
||||
"disabled_design": { "type": "string" },
|
||||
"alert_group_window_ms": { "type": "integer", "minimum": 0, "maximum": 5000 },
|
||||
"alert_group_size": { "type": "integer", "minimum": 1, "maximum": 20 },
|
||||
"alert_display_ms": { "type": "integer", "minimum": 500, "maximum": 60000 }
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/plugin-install-request.json",
|
||||
"title": "Plugin install request",
|
||||
"type": "object",
|
||||
"required": ["manifest", "wasm_base64"],
|
||||
"properties": {
|
||||
"manifest": { "$ref": "plugin-manifest.json" },
|
||||
"wasm_base64": { "type": "string", "minLength": 1, "maxLength": 5592406 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/plugin-installed-response.json",
|
||||
"title": "Installed plugin response",
|
||||
"type": "object",
|
||||
"required": ["id", "plugin_id", "name", "version", "manifest", "enabled", "installed_at", "updated_at"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "format": "uuid" },
|
||||
"plugin_id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"version": { "type": "string" },
|
||||
"manifest": { "$ref": "plugin-manifest.json" },
|
||||
"enabled": { "type": "boolean" },
|
||||
"installed_at": { "type": "string" },
|
||||
"updated_at": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/plugin-manifest.json",
|
||||
"title": "Trixie WebAssembly plugin manifest",
|
||||
"type": "object",
|
||||
"required": ["id", "name", "version", "api_version", "permissions", "entrypoint"],
|
||||
"properties": {
|
||||
"id": { "type": "string", "pattern": "^[a-z0-9.-]+$", "maxLength": 64 },
|
||||
"name": { "type": "string", "minLength": 1, "maxLength": 128 },
|
||||
"version": { "type": "string", "minLength": 1, "maxLength": 64 },
|
||||
"api_version": { "type": "string", "maxLength": 32 },
|
||||
"permissions": { "type": "array", "maxItems": 16, "items": { "enum": ["chat.read", "chat.send", "overlay.emit", "twitch.read", "twitch.manage"] } },
|
||||
"required_secrets": { "type": "array", "maxItems": 16, "items": { "type": "string", "pattern": "^[A-Za-z0-9_]{1,100}$" } },
|
||||
"entrypoint": { "type": "string", "minLength": 1, "maxLength": 64 }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.local/contracts/v1/plugin-secret-binding-request.json",
|
||||
"title": "Plugin secret binding request",
|
||||
"type": "object",
|
||||
"required": ["secret_name", "approved"],
|
||||
"properties": {
|
||||
"secret_name": { "type": "string", "pattern": "^[A-Za-z0-9_]{1,100}$" },
|
||||
"approved": { "type": "boolean" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://trixie.invalid/contracts/v1/server-info.json",
|
||||
"title": "Trixie Server Info",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"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" }
|
||||
}
|
||||
}
|
||||
@@ -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 } }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,26 @@
|
||||
# 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.
|
||||
|
||||
The restored-session contract has Draft 2020-12 validation for both credential
|
||||
replacement states, missing fields, wrong types, and unexpected properties:
|
||||
|
||||
```sh
|
||||
uv run --with jsonschema==4.26.0 python tests/session_contract.py
|
||||
```
|
||||
|
||||
This isolated validation dependency is tooling only, not an application dependency.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"session_token": "fixture-session-token-never-use",
|
||||
"must_replace_credentials": true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"authenticated": true,
|
||||
"must_replace_credentials": false
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"authenticated": true,
|
||||
"must_replace_credentials": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"filename": "trixie-20261025T030000Z.dump",
|
||||
"size_bytes": 4096,
|
||||
"modified_at": "2026-10-25T03:00:01Z"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"filename": "trixie-20261025T030000Z.dump",
|
||||
"reason": null
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"filename": "trixie-20261025T030000Z.dump",
|
||||
"reason": null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "count",
|
||||
"kind": "number",
|
||||
"required": true,
|
||||
"default": null,
|
||||
"minimum": 1,
|
||||
"maximum": 5,
|
||||
"choices": []
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"response": "Hello Alice",
|
||||
"remaining_cooldown_seconds": null,
|
||||
"error": null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"template": "Hello ${user_name}; status=${status}",
|
||||
"variables": { "user_name": "PreviewUser" },
|
||||
"sample_response": { "status": "ok" },
|
||||
"use_real_request": false,
|
||||
"response_fields": [["status", "$.status"]]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"rendered": "Hello PreviewUser; status=ok",
|
||||
"extracted_variables": { "status": "ok" },
|
||||
"error": null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"user_id": "123456789",
|
||||
"response_template": "Welcome back, ${user_name}!"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"message_id": "synthetic-message-id",
|
||||
"is_sent": true,
|
||||
"drop_reason": null
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"guest_star_available": true,
|
||||
"guest_star_reason": null,
|
||||
"shared_chat_available": true,
|
||||
"shared_chat_reason": null
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"id": "guest-star-session-1",
|
||||
"guests": [
|
||||
{
|
||||
"user_id": "123456",
|
||||
"user_display_name": "GuestOne",
|
||||
"user_login": "guestone",
|
||||
"slot_id": "1",
|
||||
"is_live": true,
|
||||
"volume": 85,
|
||||
"assigned_at": "2026-10-25T12:01:00Z",
|
||||
"audio_settings": {
|
||||
"is_host_enabled": true,
|
||||
"is_guest_enabled": true,
|
||||
"is_available": true
|
||||
},
|
||||
"video_settings": {
|
||||
"is_host_enabled": true,
|
||||
"is_guest_enabled": false,
|
||||
"is_available": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": "123456",
|
||||
"login": "example_streamer",
|
||||
"display_name": "ExampleStreamer"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"twitch_user_id": "123456",
|
||||
"display_name": "ExampleStreamer",
|
||||
"favorite": true,
|
||||
"last_invited_at": "2026-10-25T12:00:00Z"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"code": "unauthorized",
|
||||
"message": "Authentication is required.",
|
||||
"request_id": "req_fixture_unauthorized"
|
||||
}
|
||||
@@ -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,7 @@
|
||||
{
|
||||
"is_moderator_send_live_enabled": true,
|
||||
"slot_count": 4,
|
||||
"is_browser_source_audio_enabled": false,
|
||||
"group_layout": "TILED_LAYOUT",
|
||||
"browser_source_token": "fixture-token-never-use"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[
|
||||
{
|
||||
"user_id": "144601104",
|
||||
"invited_at": "2026-10-25T12:00:00Z",
|
||||
"status": "READY",
|
||||
"is_video_enabled": true,
|
||||
"is_audio_enabled": false
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"action": "timeout",
|
||||
"applied": true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"slug": "followers",
|
||||
"html": "<div id=\"alert\"></div>",
|
||||
"css": "#alert { color: white; }",
|
||||
"javascript": "",
|
||||
"enabled": true,
|
||||
"disabled_design": "transparent",
|
||||
"alert_group_window_ms": 200,
|
||||
"alert_group_size": 5,
|
||||
"alert_display_ms": 4000
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"id": "00000000-0000-4000-8000-000000000001",
|
||||
"plugin_id": "example.plugin",
|
||||
"name": "Example",
|
||||
"version": "1.0.0",
|
||||
"manifest": {
|
||||
"id": "example.plugin",
|
||||
"name": "Example",
|
||||
"version": "1.0.0",
|
||||
"api_version": "v1",
|
||||
"permissions": ["chat.read"],
|
||||
"entrypoint": "trixie_start"
|
||||
},
|
||||
"enabled": true,
|
||||
"installed_at": "2026-10-25T00:00:00Z",
|
||||
"updated_at": "2026-10-25T00:00:00Z"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"secret_name": "VTS_API_TOKEN",
|
||||
"approved": true
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"status": "ready"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"frame_type": "authenticate",
|
||||
"token": "synthetic-session-token"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Validate restored-session fixtures and rejection cases against Draft 2020-12."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
schema = json.loads((root / "contracts/v1/auth-session-response.json").read_text())
|
||||
Draft202012Validator.check_schema(schema)
|
||||
validator = Draft202012Validator(schema)
|
||||
|
||||
for name in ["auth-session.authenticated.json", "auth-session.bootstrap.json"]:
|
||||
validator.validate(json.loads((root / "fixtures/v1" / name).read_text()))
|
||||
|
||||
for invalid in [
|
||||
{"authenticated": True},
|
||||
{"authenticated": False, "must_replace_credentials": False},
|
||||
{"authenticated": True, "must_replace_credentials": "false"},
|
||||
{"authenticated": True, "must_replace_credentials": False, "unexpected": True},
|
||||
]:
|
||||
assert not validator.is_valid(invalid), "Invalid session response was accepted"
|
||||
|
||||
print("Session schema: two valid fixtures and four rejection cases passed")
|
||||
Reference in New Issue
Block a user