protocol: add canonical Phase 3A contract sources

This commit is contained in:
sechmachine
2026-07-21 23:42:13 +07:00
parent 75edd9941b
commit 2d69357052
16 changed files with 1052 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
# VerseVDI Protocol repository guidance
This repository is the sole source of the VerseVDI Phase 3 wire contract. It
contains OpenAPI, JSON Schema, Protobuf, registries, fixtures, and generated
language bindings. It contains no Connection Server business policy,
database/auth implementation, provider credentials, or media implementation.
## Source and generation rules
- Edit `schemas/`, `openapi/`, `proto/`, `registries/`, and `fixtures/` only as
protocol sources. Never hand-edit `gen/`; run `python3 tools/generate.py`.
- `VERSION` and `compatibility.json` are the version authority. Current, N-1,
and N-2 fixtures must remain executable.
- `make verify` runs schema/fixture checks, Protobuf lint/descriptor checks,
the historical-descriptor breaking check, deterministic generation, Go tests,
and native conformance runners.
- Generated output must be byte-for-byte clean after a second generation.
- `gen/protobuf/phase3a-baseline.binpb` is a generated historical descriptor used
only by Buf breaking checks; regenerate it from an approved baseline tag,
never by hand.
- The Connection Server consumes generated Go types through adapters; it does
not own a competing wire schema.
## Scope boundaries
Phase 3A covers control, authorization, resources, broker session actions,
gateway-only grants/manifests, resumable control events, registered control
datagrams, and bounded text clipboard. It does not implement a gateway,
provider transport, video/audio forwarding, file transfer, or a second auth or
datastore path.
## Git and release safety
Keep private Gitea work and public-source boundaries explicit. Preserve dirty
work from other owners. Do not push, merge, publish, or change release
credentials unless the task separately authorizes it. A local immutable
candidate tag is allowed only after matching Go/Rust/Swift fixture hashes and
clean-checkout evidence; it is not a publication.
+25
View File
@@ -1,2 +1,27 @@
# VerseVDI-Protocol # VerseVDI-Protocol
The Phase 3A Protocol repository is the sole source of VerseVDI wire contracts.
OpenAPI, JSON Schema, Protobuf, registries, and conformance fixtures live here;
the Connection Server consumes generated Go bindings and remains the authority
for authentication, authorization, persistence, licensing, and provider policy.
## Scope
Phase 3A defines versioned control requests/responses, native device contracts,
subject-scoped resources, broker actions, gateway-only manifests, resumable
control events, registered control datagrams, and bounded text clipboard. It
does not implement a gateway, provider transport, media forwarding, file
transfer, or a second authentication or datastore path.
## Verification
Run `make verify` after editing a source schema, registry, Protobuf definition,
or fixture. The command validates source bounds and scope, regenerates Go/Rust/
Swift bindings and Protobuf descriptors, compiles generated bindings, runs the
shared Go/Rust/Swift corpus, checks datagram frames, and fails if regeneration
would dirty the checkout.
Generated files under `gen/` are never edited by hand. `VERSION`,
`compatibility.json`, and the content-addressed fixture manifest identify the
release inputs. A Protocol release candidate is not valid until all consumers
report the same fixture hash and clean-checkout regeneration evidence.
+1
View File
@@ -0,0 +1 @@
1.0.0
+9
View File
@@ -0,0 +1,9 @@
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
breaking:
use:
- FILE
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"protocol": "versevdi-control",
"current": "1",
"n_minus_1": "0",
"n_minus_2": "-1",
"unsupported": ["-2", "2"],
"feature_registry": "registries/features.json",
"datagram_registry": "registries/datagrams.json"
}
+43
View File
@@ -0,0 +1,43 @@
# Phase 3A interface inventory
This inventory records the wire boundary consumed by the closed Connection Server.
The Protocol repository owns only schemas, registries, fixtures, and generated bindings;
the Server remains the authority for authentication, authorization, persistence, and
provider selection.
## Existing Server routes adapted by generated contracts
| Route | Channel/guard | Contract boundary | Phase 2 behavior |
|---|---|---|---|
| `POST /api/v1/auth/refresh` | native credential body; rate limited | `RefreshRequest` -> `NativeCredential` | opaque refresh rotation and replay-family revocation remain PostgreSQL-owned |
| `POST /api/v1/auth/devices` | authenticated browser session + CSRF | `DeviceRegistrationRequest` -> `DeviceChallenge` | device challenge remains one-use and hash-backed |
| `POST /api/v1/auth/devices/:id/prove` | authenticated browser session + CSRF | `DeviceProofRequest` -> `NativeCredential` | canonical Ed25519 proof and native session issuance remain Server-owned |
| `DELETE /api/v1/auth/devices/:id` | authenticated browser session + CSRF | no body | principal-scoped device revocation remains Server-owned |
| `GET /api/v1/resources` | authenticated browser/native session | `ResourceList` | assigned desktop and entitled pool projections omit machine/provider data |
| `POST /api/v1/broker/sessions` | authenticated session + CSRF | `SessionRequest` -> `BrokerSession` | existing assignment, idempotency, queue, and PostgreSQL state machine remain authoritative |
| `GET /api/v1/broker/sessions/:id` | authenticated session | `BrokerSession` | owner-scoped lookup is preserved |
| `POST /api/v1/broker/sessions/:id/allocate` | authenticated session + CSRF | `ConnectionManifest` | gateway-only grant boundary; no provider/direct-host fields |
| `POST /api/v1/broker/sessions/:id/reconnect` | authenticated session + CSRF | `ReconnectRequest` -> `ConnectionManifest` | device proof, expected version, grant rotation, and monotonic reconnect sequence remain authoritative |
| `POST /api/v1/broker/sessions/:id/cancel` | authenticated session + CSRF | no body | existing compensation path remains authoritative |
## Contract rules
- Unknown fields are rejected for signed/security-sensitive requests and generated
decoders are strict.
- Responses are explicit projections; domain, sqlc, provider, VM, and credential
structures are never serialized directly.
- Resource projections contain only opaque assignment/pool identifiers and safe state;
machine identifiers and provider endpoints are not wire fields.
- A manifest contains only the versioned gateway/tunnel/profile/grant allow-list. The
opaque grant is stored as a hash in the Server database and is single-use.
- Events and QUIC/tunnel sources are defined here before any production media or gateway
implementation; the Phase 3A claim stops at control contracts and conformance.
## Compatibility notes
The existing routes and authentication channels remain unchanged. Phase 3A adds the
`page` member to the resource response and replaces the legacy flat manifest projection
with the versioned gateway-only manifest. Both changes are intentional contract changes:
the former is additive, and the latter is required to make provider/direct-host fields
unrepresentable. The Server integration tests retain the old safe resource members and
assert the new allow-list explicitly.
+27
View File
@@ -0,0 +1,27 @@
# VerseVDI control datagram v1
Phase 3A reserves a bounded control-datagram envelope. It does not forward video,
audio, provider traffic, VM traffic, or arbitrary binary clipboard data.
All multi-byte integers are unsigned big-endian. The fixed header is 21 bytes:
| Offset | Size | Field | Rule |
|---:|---:|---|---|
| 0 | 2 | magic | ASCII `VD` (`0x56 0x44`) |
| 2 | 1 | version | `1` only |
| 3 | 1 | channel | registered channel identifier |
| 4 | 1 | flags | registry-defined; unknown bits reject |
| 5 | 4 | sequence | session-local wrapping sequence |
| 9 | 8 | timestamp_ms | sender timestamp, bounded by transport skew policy |
| 17 | 1 | fragment_index | zero-based; `0` when not fragmented |
| 18 | 1 | fragment_count | `1` when not fragmented; index less than count |
| 19 | 2 | payload_length | exact payload byte count |
The complete frame is at most 65,536 bytes and `payload_length` is at most 65,515.
Truncated, oversized, unknown-version, unknown-channel, invalid-fragment, and
length-mismatch frames are rejected before allocation proportional to the claimed
payload. Media/provider identifiers are not registered channels.
Registered Phase 3A channels are `control.ack.v1`, `control.cancel.v1`, and
`clipboard.text.v1`. Clipboard payloads are UTF-8 JSON text contracts and remain
subject to the 65,536-byte text limit and explicit authorization.
+12
View File
@@ -0,0 +1,12 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"version": "1",
"header_bytes": 21,
"maximum_frame_bytes": 65536,
"channels": [
{"id": 1, "name": "control.ack.v1", "direction": "bidirectional", "max_payload_bytes": 1024},
{"id": 2, "name": "control.cancel.v1", "direction": "client-to-server", "max_payload_bytes": 2048},
{"id": 3, "name": "clipboard.text.v1", "direction": "bidirectional", "max_payload_bytes": 65515}
],
"reserved_rejected": ["video", "audio", "provider", "vm", "file-transfer", "clipboard.binary"]
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/sechmachine/VerseVDI-Protocol
go 1.26
+414
View File
@@ -0,0 +1,414 @@
openapi: 3.1.0
info:
title: VerseVDI control contract
version: 1.0.0
description: Bounded Phase 3A control-plane wire contract; no provider or media endpoints.
servers:
- url: /
tags:
- name: authentication
- name: resources
- name: broker
- name: events
paths:
/api/v1/auth/login:
post:
tags: [authentication]
operationId: loginBrowserSession
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/LoginRequest
responses:
'200':
description: Browser session established through secure cookies.
content:
application/json:
schema:
type: object
additionalProperties: false
required: [username, provider, roles, role]
properties:
username: {type: string, minLength: 1, maxLength: 256}
provider: {type: string, minLength: 1, maxLength: 64}
roles: {type: array, maxItems: 16, items: {type: string, maxLength: 64}}
role: {type: string, enum: [user, admin]}
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'503': {$ref: '#/components/responses/Unavailable'}
/api/v1/auth/refresh:
post:
tags: [authentication]
operationId: rotateNativeCredential
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/RefreshRequest
responses:
'200':
description: Rotated opaque native credential.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/NativeCredential
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/session:
get:
tags: [authentication]
operationId: getAuthenticatedSession
security:
- browserSession: []
- nativeBearer: []
responses:
'200':
description: Authenticated subject summary.
content:
application/json:
schema:
type: object
additionalProperties: false
required: [username, provider, roles, role]
properties:
username: {type: string, minLength: 1, maxLength: 256}
provider: {type: string, minLength: 1, maxLength: 64}
roles: {type: array, maxItems: 16, items: {type: string, maxLength: 64}}
role: {type: string, enum: [user, admin]}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/reauth:
post:
tags: [authentication]
operationId: issueReauthenticationGrant
security:
- browserSession: []
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ReauthRequest
responses:
'200':
description: Short-lived opaque step-up grant.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ReauthGrant
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/logout:
post:
tags: [authentication]
operationId: logoutSession
security:
- browserSession: []
- nativeBearer: []
responses:
'204': {description: Session revoked and browser cookies cleared.}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/devices:
post:
tags: [authentication]
operationId: registerDevice
security:
- browserSession: []
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/DeviceRegistrationRequest
responses:
'201':
description: Single-use device challenge.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/DeviceChallenge
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/devices/{device_id}/prove:
post:
tags: [authentication]
operationId: proveDevice
security:
- browserSession: []
parameters:
- $ref: '#/components/parameters/DeviceID'
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/DeviceProofRequest
responses:
'200':
description: Opaque native credential.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/NativeCredential
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/auth/devices/{device_id}:
delete:
tags: [authentication]
operationId: revokeDevice
security:
- browserSession: []
parameters:
- $ref: '#/components/parameters/DeviceID'
responses:
'204': {description: Device and its native authority revoked.}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/resources:
get:
tags: [resources]
operationId: listResources
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Cursor'
responses:
'200':
description: Subject-scoped bounded resources and entitlements.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ResourceList
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
/api/v1/broker/sessions:
post:
tags: [broker]
operationId: requestBrokerSession
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/SessionRequest
responses:
'201':
description: Durable broker session.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession
'202':
description: Durable session waiting for an eligible desktop or capacity.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'403': {$ref: '#/components/responses/Forbidden'}
'409': {$ref: '#/components/responses/Conflict'}
/api/v1/broker/sessions/{session_id}:
get:
tags: [broker]
operationId: getBrokerSession
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/SessionID'
responses:
'200':
description: Subject-scoped broker session projection.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
/api/v1/broker/sessions/{session_id}/allocate:
post:
tags: [broker]
operationId: allocateBrokerSession
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/SessionID'
responses:
'200':
description: Gateway-only launch manifest with a single-use grant.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ConnectionManifest
'202':
description: Session remains waiting for an eligible desktop or capacity.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/BrokerSession
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
'409': {$ref: '#/components/responses/Conflict'}
/api/v1/broker/sessions/{session_id}/reconnect:
post:
tags: [broker]
operationId: reconnectBrokerSession
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/SessionID'
requestBody:
required: true
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ReconnectRequest
responses:
'200':
description: Gateway-only reconnect manifest with a monotonic sequence.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ConnectionManifest
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
'409': {$ref: '#/components/responses/Conflict'}
/api/v1/broker/sessions/{session_id}/cancel:
post:
tags: [broker]
operationId: cancelBrokerSession
security:
- browserSession: []
- nativeBearer: []
parameters:
- $ref: '#/components/parameters/SessionID'
responses:
'204': {description: Cancellation accepted idempotently.}
'401': {$ref: '#/components/responses/Unauthorized'}
'404': {$ref: '#/components/responses/NotFound'}
'409': {$ref: '#/components/responses/Conflict'}
/api/v1/events:
get:
tags: [events]
operationId: resumeUserEvents
security:
- browserSession: []
- nativeBearer: []
parameters:
- name: version
in: query
required: false
schema: {type: string, enum: ['1']}
- name: cursor
in: query
required: false
schema: {type: string, maxLength: 512}
- name: Upgrade
in: header
required: false
schema: {type: string, enum: [websocket]}
- name: Sec-WebSocket-Version
in: header
required: false
schema: {type: string, enum: ['13']}
responses:
'200':
description: Bounded resumable NDJSON event snapshot; WebSocket upgrades emit the same envelopes as text frames.
headers:
X-VerseVDI-Event-Cursor:
schema: {type: string, maxLength: 512}
content:
application/x-ndjson:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/EventEnvelope
'400': {$ref: '#/components/responses/InvalidRequest'}
'401': {$ref: '#/components/responses/Unauthorized'}
'409': {$ref: '#/components/responses/Conflict'}
'426': {description: WebSocket version 13 is required.}
'503': {$ref: '#/components/responses/Unavailable'}
components:
securitySchemes:
browserSession:
type: apiKey
in: cookie
name: versevdi_session
nativeBearer:
type: http
scheme: bearer
bearerFormat: opaque
parameters:
DeviceID:
name: device_id
in: path
required: true
schema: {type: string, minLength: 1, maxLength: 128}
SessionID:
name: session_id
in: path
required: true
schema: {type: string, minLength: 1, maxLength: 128}
Limit:
name: limit
in: query
required: false
schema: {type: integer, minimum: 1, maximum: 100, default: 100}
Cursor:
name: cursor
in: query
required: false
schema: {type: string, maxLength: 512}
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema: {type: string, minLength: 1, maxLength: 256}
responses:
InvalidRequest:
description: The request is invalid.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
Unauthorized:
description: Authentication or credential validation failed without revealing credential state.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
Forbidden:
description: The authenticated subject is not entitled to the requested action.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
NotFound:
description: The resource is unavailable to this subject.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
Conflict:
description: The expected version, protocol version, idempotency, or event retention boundary conflicts.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
Unavailable:
description: The authoritative service is temporarily unavailable.
content:
application/json:
schema:
$ref: ../schemas/control-v1.schema.json#/$defs/ErrorEnvelope
+20
View File
@@ -0,0 +1,20 @@
schema: spec-driven
# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform
# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours
+34
View File
@@ -0,0 +1,34 @@
syntax = "proto3";
package versevdi.control.v1;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/sechmachine/VerseVDI-Protocol/gen/go/protocol";
message VersionNegotiation {
repeated string supported_versions = 1;
repeated string features = 2;
}
message ResourceLink {
string type = 1;
string id = 2;
int64 version = 3;
}
message EventEnvelope {
string event_id = 1;
int64 sequence = 2;
string type = 3;
int64 version = 4;
ResourceLink resource = 5;
google.protobuf.Timestamp occurred_at = 6;
bytes payload_json = 7;
string correlation_id = 8;
}
message ClipboardText {
string text = 1;
string encoding = 2;
}
+51
View File
@@ -0,0 +1,51 @@
syntax = "proto3";
package versevdi.tunnel.v1;
import "google/protobuf/timestamp.proto";
option go_package = "github.com/sechmachine/VerseVDI-Protocol/gen/go/tunnel";
message VersionNegotiation {
repeated string supported_versions = 1;
repeated string features = 2;
}
message AdmissionRequest {
string session_id = 1;
string protocol_version = 2;
string audience = 3;
string purpose = 4;
string grant = 5;
}
message AdmissionResult {
bool admitted = 1;
string session_id = 2;
string failure_code = 3;
int64 session_version = 4;
}
message SessionClose {
string session_id = 1;
string code = 2;
string reason = 3;
google.protobuf.Timestamp occurred_at = 4;
}
message Health {
string session_id = 1;
int64 sequence = 2;
google.protobuf.Timestamp observed_at = 3;
}
message BoundedError {
string code = 1;
string message = 2;
}
message ChannelOpen {
string channel_id = 1;
string direction = 2;
uint32 maximum_frame_bytes = 3;
}
+9
View File
@@ -0,0 +1,9 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"max_frame_bytes": 65536,
"datagrams": [
{"id": "control.ack.v1", "direction": "bidirectional", "max_payload_bytes": 1024},
{"id": "control.cancel.v1", "direction": "client-to-server", "max_payload_bytes": 2048},
{"id": "clipboard.text.v1", "direction": "bidirectional", "max_payload_bytes": 65536}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"features": [
{"id": "control.v1", "direction": "bidirectional", "max_frame_bytes": 65536},
{"id": "events.resume.v1", "direction": "server-to-client", "max_frame_bytes": 65536},
{"id": "clipboard.text.v1", "direction": "bidirectional", "max_frame_bytes": 65536}
]
}
+348
View File
@@ -0,0 +1,348 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://protocol.versevdi.invalid/schemas/control-v1.schema.json",
"title": "VerseVDI Phase 3A control contract",
"type": "object",
"additionalProperties": false,
"$defs": {
"FieldViolation": {
"type": "object",
"additionalProperties": false,
"required": ["field", "code"],
"properties": {
"field": {"type": "string", "minLength": 1, "maxLength": 128},
"code": {"type": "string", "minLength": 1, "maxLength": 64}
}
},
"ErrorEnvelope": {
"type": "object",
"additionalProperties": false,
"required": ["status", "error", "code", "message", "resolution", "request_id", "violations"],
"properties": {
"status": {"type": "boolean"},
"error": {"type": "string", "minLength": 1, "maxLength": 512},
"code": {"type": "string", "minLength": 1, "maxLength": 128},
"message": {"type": "string", "minLength": 1, "maxLength": 512},
"resolution": {"type": "string", "minLength": 1, "maxLength": 128},
"request_id": {"type": "string", "minLength": 1, "maxLength": 128},
"violations": {"type": "array", "maxItems": 16, "items": {"$ref": "#/$defs/FieldViolation"}}
}
},
"PageInfo": {
"type": "object",
"additionalProperties": false,
"required": ["limit", "next_cursor"],
"properties": {
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
"next_cursor": {"type": "string", "maxLength": 512}
}
},
"ResourceLink": {
"type": "object",
"additionalProperties": false,
"required": ["type", "id", "version"],
"properties": {
"type": {"type": "string", "minLength": 1, "maxLength": 64},
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"version": {"type": "integer", "minimum": 1}
}
},
"Resource": {
"type": "object",
"additionalProperties": false,
"required": ["id", "kind", "name", "state", "version", "links"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"kind": {"type": "string", "minLength": 1, "maxLength": 64},
"name": {"type": "string", "minLength": 1, "maxLength": 256},
"state": {"type": "string", "minLength": 1, "maxLength": 64},
"assignment_state": {"type": "string", "maxLength": 64},
"version": {"type": "integer", "minimum": 1},
"links": {"type": "array", "maxItems": 16, "items": {"$ref": "#/$defs/ResourceLink"}}
}
},
"AssignedDesktop": {
"type": "object",
"additionalProperties": false,
"required": ["assignment_id", "pool_id", "name", "availability"],
"properties": {
"assignment_id": {"type": "string", "minLength": 1, "maxLength": 128},
"pool_id": {"type": "string", "minLength": 1, "maxLength": 128},
"name": {"type": "string", "minLength": 1, "maxLength": 256},
"availability": {"type": "string", "minLength": 1, "maxLength": 64}
}
},
"EntitledPool": {
"type": "object",
"additionalProperties": false,
"required": ["pool_id", "name", "assignment_state"],
"properties": {
"pool_id": {"type": "string", "minLength": 1, "maxLength": 128},
"name": {"type": "string", "minLength": 1, "maxLength": 256},
"assignment_state": {"type": "string", "minLength": 1, "maxLength": 64}
}
},
"ResourceList": {
"type": "object",
"additionalProperties": false,
"required": ["assigned_desktops", "entitled_pools", "page"],
"properties": {
"assigned_desktops": {"type": "array", "maxItems": 100, "items": {"$ref": "#/$defs/AssignedDesktop"}},
"entitled_pools": {"type": "array", "maxItems": 100, "items": {"$ref": "#/$defs/EntitledPool"}},
"page": {"$ref": "#/$defs/PageInfo"}
}
},
"DeviceRegistrationRequest": {
"type": "object",
"additionalProperties": false,
"required": ["name", "platform", "device_subject", "algorithm", "public_key"],
"properties": {
"name": {"type": "string", "minLength": 1, "maxLength": 128},
"platform": {"type": "string", "minLength": 1, "maxLength": 64},
"device_subject": {"type": "string", "minLength": 1, "maxLength": 256},
"algorithm": {"type": "string", "const": "ed25519"},
"public_key": {"type": "string", "minLength": 1, "maxLength": 256}
}
},
"DeviceChallenge": {
"type": "object",
"additionalProperties": false,
"required": ["device_id", "server_id", "principal_id", "challenge", "expires_at", "algorithm", "signature_format"],
"properties": {
"device_id": {"type": "string", "minLength": 1, "maxLength": 128},
"server_id": {"type": "string", "minLength": 1, "maxLength": 128},
"principal_id": {"type": "string", "minLength": 1, "maxLength": 128},
"challenge": {"type": "string", "minLength": 1, "maxLength": 256},
"expires_at": {"type": "string", "format": "date-time", "maxLength": 64},
"algorithm": {"type": "string", "const": "ed25519"},
"signature_format": {"type": "string", "const": "ed25519-domain-separated-v1"}
}
},
"DeviceProofRequest": {
"type": "object",
"additionalProperties": false,
"required": ["challenge", "signature"],
"properties": {
"challenge": {"type": "string", "minLength": 1, "maxLength": 256},
"signature": {"type": "string", "minLength": 1, "maxLength": 256}
}
},
"LoginRequest": {
"type": "object",
"additionalProperties": false,
"required": ["username", "password"],
"properties": {
"provider": {"type": "string", "enum": ["ldap", "local"]},
"username": {"type": "string", "minLength": 1, "maxLength": 256},
"password": {"type": "string", "minLength": 1, "maxLength": 1024}
}
},
"ReauthRequest": {
"type": "object",
"additionalProperties": false,
"required": ["password", "purpose"],
"properties": {
"password": {"type": "string", "minLength": 1, "maxLength": 1024},
"purpose": {"type": "string", "enum": ["identity_change", "key_change", "backup_enable", "external_database_tls_disabled", "assignment_change"]}
}
},
"ReauthGrant": {
"type": "object",
"additionalProperties": false,
"required": ["token", "purpose", "expires_at"],
"properties": {
"token": {"type": "string", "minLength": 1, "maxLength": 256},
"purpose": {"type": "string", "minLength": 1, "maxLength": 64},
"expires_at": {"type": "string", "format": "date-time", "maxLength": 64}
}
},
"RefreshRequest": {
"type": "object",
"additionalProperties": false,
"required": ["family_id", "refresh_token"],
"properties": {
"family_id": {"type": "string", "minLength": 1, "maxLength": 128},
"refresh_token": {"type": "string", "minLength": 1, "maxLength": 256}
}
},
"NativeCredential": {
"type": "object",
"additionalProperties": false,
"required": ["family_id", "access_token", "refresh_token", "expires_at"],
"properties": {
"device_id": {"type": "string", "maxLength": 128},
"family_id": {"type": "string", "minLength": 1, "maxLength": 128},
"access_token": {"type": "string", "minLength": 1, "maxLength": 256},
"refresh_token": {"type": "string", "minLength": 1, "maxLength": 256},
"expires_at": {"type": "string", "format": "date-time", "maxLength": 64},
"refresh_expires_at": {"type": "string", "format": "date-time", "maxLength": 64}
}
},
"SessionRequest": {
"type": "object",
"additionalProperties": false,
"required": ["client_device_id", "device_key_id", "pool_id", "idempotency_key", "policy_snapshot"],
"properties": {
"client_device_id": {"type": "string", "minLength": 1, "maxLength": 128},
"device_key_id": {"type": "string", "minLength": 1, "maxLength": 128},
"pool_id": {"type": "string", "minLength": 1, "maxLength": 128},
"idempotency_key": {"type": "string", "minLength": 1, "maxLength": 256},
"policy_snapshot": {"$ref": "#/$defs/AllocationPolicy"}
}
},
"ReconnectRequest": {
"type": "object",
"additionalProperties": false,
"required": ["client_device_id", "device_key_id", "expected_version"],
"properties": {
"client_device_id": {"type": "string", "minLength": 1, "maxLength": 128},
"device_key_id": {"type": "string", "minLength": 1, "maxLength": 128},
"expected_version": {"type": "integer", "minimum": 1}
}
},
"BrokerSession": {
"type": "object",
"additionalProperties": false,
"required": ["id", "principal_id", "pool_id", "state", "policy_snapshot", "cleanup_state", "idempotency_key", "correlation_id", "requested_at", "version"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"principal_id": {"type": "string", "minLength": 1, "maxLength": 128},
"pool_id": {"type": "string", "minLength": 1, "maxLength": 128},
"assignment_id": {"type": "string", "maxLength": 128},
"state": {"type": "string", "minLength": 1, "maxLength": 64},
"policy_snapshot": {"$ref": "#/$defs/AllocationPolicy"},
"reconnect_deadline": {"type": "string", "format": "date-time", "maxLength": 64},
"outcome": {"type": "string", "maxLength": 64},
"failure_code": {"type": "string", "maxLength": 128},
"cleanup_state": {"type": "string", "minLength": 1, "maxLength": 64},
"idempotency_key": {"type": "string", "minLength": 1, "maxLength": 256},
"correlation_id": {"type": "string", "minLength": 1, "maxLength": 128},
"requested_at": {"type": "string", "format": "date-time", "maxLength": 64},
"ended_at": {"type": "string", "format": "date-time", "maxLength": 64},
"version": {"type": "integer", "minimum": 1}
}
},
"AllocationPolicy": {
"type": "object",
"additionalProperties": false,
"required": ["minimum_kbps", "target_kbps", "maximum_kbps", "tier", "audience", "protocol", "protocol_version", "grant_ttl_seconds", "reservation_lease_seconds"],
"properties": {
"minimum_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000},
"target_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000},
"maximum_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000},
"tier": {"type": "string", "enum": ["standard", "priority", "premium"]},
"audience": {"type": "string", "minLength": 1, "maxLength": 256},
"protocol": {"type": "string", "minLength": 1, "maxLength": 64},
"protocol_version": {"type": "integer", "minimum": 1, "maximum": 100},
"grant_ttl_seconds": {"type": "integer", "minimum": 5, "maximum": 300},
"reservation_lease_seconds": {"type": "integer", "minimum": 5, "maximum": 3600}
}
},
"ConnectionManifest": {
"type": "object",
"additionalProperties": false,
"required": ["version", "purpose", "session_id", "reconnect_sequence", "gateway", "tunnel", "profile", "grant", "correlation_id"],
"properties": {
"version": {"type": "string", "const": "1"},
"purpose": {"type": "string", "enum": ["launch", "reconnect"]},
"session_id": {"type": "string", "minLength": 1, "maxLength": 128},
"reconnect_sequence": {"type": "integer", "minimum": 0},
"gateway": {"$ref": "#/$defs/ManifestGateway"},
"tunnel": {"$ref": "#/$defs/ManifestTunnel"},
"profile": {"$ref": "#/$defs/ManifestProfile"},
"grant": {"$ref": "#/$defs/GrantReference"},
"correlation_id": {"type": "string", "minLength": 1, "maxLength": 128}
}
},
"ManifestGateway": {
"type": "object",
"additionalProperties": false,
"required": ["id", "addresses", "public_identity"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"addresses": {"type": "array", "minItems": 1, "maxItems": 4, "items": {"type": "string", "minLength": 1, "maxLength": 256}},
"public_identity": {"type": "string", "minLength": 1, "maxLength": 256}
}
},
"ManifestTunnel": {
"type": "object",
"additionalProperties": false,
"required": ["versions", "features"],
"properties": {
"versions": {"type": "array", "minItems": 1, "maxItems": 4, "items": {"type": "string", "minLength": 1, "maxLength": 64}},
"features": {"type": "array", "maxItems": 32, "items": {"type": "string", "minLength": 1, "maxLength": 64}}
}
},
"ManifestProfile": {
"type": "object",
"additionalProperties": false,
"required": ["id", "bounds"],
"properties": {
"id": {"type": "string", "minLength": 1, "maxLength": 128},
"bounds": {"$ref": "#/$defs/ManifestBounds"}
}
},
"ManifestBounds": {
"type": "object",
"additionalProperties": false,
"required": ["minimum_kbps", "target_kbps", "maximum_kbps"],
"properties": {
"minimum_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000},
"target_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000},
"maximum_kbps": {"type": "integer", "minimum": 1, "maximum": 100000000}
}
},
"GrantReference": {
"type": "object",
"additionalProperties": false,
"required": ["opaque_value", "expires_at", "audience"],
"properties": {
"opaque_value": {"type": "string", "minLength": 43, "maxLength": 256},
"expires_at": {"type": "string", "format": "date-time", "maxLength": 64},
"audience": {"type": "string", "minLength": 1, "maxLength": 128}
}
},
"EventResume": {
"type": "object",
"additionalProperties": false,
"required": ["cursor", "last_sequence"],
"properties": {
"cursor": {"type": "string", "maxLength": 512},
"last_sequence": {"type": "integer", "minimum": 0}
}
},
"EventEnvelope": {
"type": "object",
"additionalProperties": false,
"required": ["event_id", "sequence", "type", "version", "resource", "occurred_at", "correlation_id", "payload"],
"properties": {
"event_id": {"type": "string", "minLength": 1, "maxLength": 128},
"sequence": {"type": "integer", "minimum": 1},
"type": {"type": "string", "minLength": 1, "maxLength": 128},
"version": {"type": "integer", "minimum": 1},
"resource": {"$ref": "#/$defs/ResourceLink"},
"occurred_at": {"type": "string", "format": "date-time", "maxLength": 64},
"correlation_id": {"type": "string", "minLength": 1, "maxLength": 128},
"payload": {"type": "object", "x-max-bytes": 16384}
}
},
"ClipboardText": {
"type": "object",
"additionalProperties": false,
"required": ["text", "encoding"],
"properties": {
"text": {"type": "string", "maxLength": 65536},
"encoding": {"type": "string", "const": "utf-8"}
}
},
"VersionNegotiation": {
"type": "object",
"additionalProperties": false,
"required": ["supported_versions", "features"],
"properties": {
"supported_versions": {"type": "array", "minItems": 1, "maxItems": 3, "items": {"type": "string", "maxLength": 16}},
"features": {"type": "array", "maxItems": 64, "items": {"type": "string", "maxLength": 64}}
}
}
}
}