proto: freeze the wire contract at 1.0.0

Schemas for the whole v1 surface: 38 methods, 9 events, 25 named types and the
JSON-RPC envelope, with x-privileged / x-transports / x-deadlineMs / x-errors
annotations that both generators emit as data rather than prose.

Four generators over one IR (contracts/codegen/schema_ir.py), so the C++ structs,
the TypeScript types and the OpenRPC document cannot disagree about what the
contract says:

  gen_cpp.py             -> core/generated/velox_proto.{hpp,cpp}
  gen_ts.py              -> extension/src/shared/protocol/
  gen_openrpc.py         -> contracts/openrpc.json
  gen_cpp_conformance.py -> tests/conformance/cpp/fixture_dispatcher.hpp

Inbound parsing never throws: parse<T>() returns std::expected<T, ParseError> and
nlohmann's throwing ADL from_json is deliberately not emitted. Schema constraints
(minimum, maxLength, pattern, ...) become real runtime checks in both languages —
the daemon does not trust the extension and the extension does not trust the
daemon.

59 golden fixtures: a success case per method, 12 error cases, 9 events. Replayed
by tests/conformance/ against both the generated C++ and a live server over both
transports. tools/mockd serves the same fixtures with unhappy-path flags so the
GUI and EXT lanes never wait for veloxd.

run.sh also proves capture.offer fails open: with a daemon answering slower than
750 ms the client gives up and lets Firefox take the download.

core/generated/ is libveloxproto, a separate target from libveloxcore, which
still never sees JSON — see docs/adr/0009.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
This commit is contained in:
2026-09-09 19:55:54 +04:00
co-authored by Claude Opus 5
parent a40585f419
commit 53421d6cb8
171 changed files with 29275 additions and 51 deletions
@@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.auth.required.schema.json",
"title": "event.auth.required",
"description": "A server asked for credentials. The task sits in retry_wait until the client supplies them. Credentials travel to the Secret Service, never back through this event and never into a log.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"host",
"scheme"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"host": {
"type": "string"
},
"realm": {
"type": [
"string",
"null"
]
},
"scheme": {
"type": "string",
"enum": [
"basic",
"digest",
"ntlm",
"negotiate",
"proxy"
]
}
}
}
}
}
@@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.grabber.progress.schema.json",
"title": "event.grabber.progress",
"description": "Crawl progress for the Site Grabber wizard. done true means the file list in grabber.status is final.",
"x-direction": "server-to-client",
"x-maxRateHz": 4,
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"jobId",
"found",
"crawled",
"done"
],
"properties": {
"jobId": {
"type": "string"
},
"found": {
"type": "integer",
"minimum": 0
},
"crawled": {
"type": "integer",
"minimum": 0
},
"done": {
"type": "boolean"
},
"currentUrl": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
}
}
@@ -0,0 +1,57 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.notify.schema.json",
"title": "event.notify",
"description": "Something the user should see: a completion, a failure, a queue finishing. The client decides between a toast, a tray balloon and a sound; the daemon does not assume a GUI is running.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"level",
"title",
"body"
],
"properties": {
"level": {
"type": "string",
"enum": [
"info",
"success",
"warning",
"error"
]
},
"title": {
"type": "string",
"maxLength": 128
},
"body": {
"type": "string",
"maxLength": 1024
},
"taskId": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"sound": {
"type": [
"string",
"null"
],
"enum": [
"complete",
"queueComplete",
"error",
null
]
}
}
}
}
}
@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.settings.changed.schema.json",
"title": "event.settings.changed",
"description": "Settings were written by some client. Carries only the key names; a client re-reads what it cares about. The extension watches for capture.* here and re-fetches capture.getRules so its rules never lag the daemon's.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"keys"
],
"properties": {
"keys": {
"type": "array",
"items": {
"$ref": "https://velox.dev/schema/types/SettingKey.schema.json"
}
}
}
}
}
}
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.speed.global.schema.json",
"title": "event.speed.global",
"description": "Aggregate throughput for the status bar, the tray tooltip and the extension popup. Emitted at 1 Hz even when nothing is active, so a client can tell 'idle' from 'disconnected'.",
"x-direction": "server-to-client",
"x-maxRateHz": 1,
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"downBps",
"activeCount"
],
"properties": {
"downBps": {
"type": "integer",
"minimum": 0
},
"activeCount": {
"type": "integer",
"minimum": 0
},
"queuedCount": {
"type": "integer",
"minimum": 0
},
"limitBps": {
"type": [
"integer",
"null"
],
"minimum": 0,
"description": "null when the limiter is off."
}
}
}
}
}
@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.task.added.schema.json",
"title": "event.task.added",
"description": "A task entered the list. summary is always present so a client can insert the row without a follow-up download.get.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"summary"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"summary": {
"$ref": "https://velox.dev/schema/types/TaskSummary.schema.json"
}
}
}
}
}
@@ -0,0 +1,87 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.task.progress.schema.json",
"title": "event.task.progress",
"description": "Batched byte counters for every active task. Emitted at no more than 4 Hz as one array, never one notification per task: at twenty active downloads that is four messages a second instead of eighty. Clients apply a row patch and repaint the touched columns; rebuilding a model on this event is a bug.",
"x-direction": "server-to-client",
"x-maxRateHz": 4,
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"tasks",
"at"
],
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"downloadedBytes",
"speedBps"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"downloadedBytes": {
"type": "integer",
"minimum": 0
},
"speedBps": {
"type": "integer",
"minimum": 0
},
"etaSeconds": {
"type": [
"integer",
"null"
],
"minimum": 0
},
"segments": {
"type": "array",
"maxItems": 32,
"items": {
"type": "object",
"additionalProperties": false,
"description": "Only what a segment bar needs. Full segment state comes from download.get.",
"required": [
"index",
"downloadedBytes",
"speedBps"
],
"properties": {
"index": {
"type": "integer",
"minimum": 0,
"maximum": 31
},
"downloadedBytes": {
"type": "integer",
"minimum": 0
},
"speedBps": {
"type": "integer",
"minimum": 0
}
}
}
}
}
}
},
"at": {
"type": "string",
"format": "date-time"
}
}
}
}
}
@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.task.removed.schema.json",
"title": "event.task.removed",
"description": "A task left the list. The client deletes the row; there is nothing further to fetch.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"deletedFile"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"deletedFile": {
"type": "boolean"
}
}
}
}
}
@@ -0,0 +1,57 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://velox.dev/schema/events/event.task.state.schema.json",
"title": "event.task.state",
"description": "A task changed lifecycle state. Carries the summary so the row can be repainted in full without a round trip, and error whenever the new state is failed or retry_wait.",
"x-direction": "server-to-client",
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": false,
"required": [
"taskId",
"state"
],
"properties": {
"taskId": {
"type": "string",
"format": "uuid"
},
"state": {
"$ref": "https://velox.dev/schema/types/TaskState.schema.json"
},
"previousState": {
"oneOf": [
{
"$ref": "https://velox.dev/schema/types/TaskState.schema.json"
},
{
"type": "null"
}
]
},
"summary": {
"oneOf": [
{
"$ref": "https://velox.dev/schema/types/TaskSummary.schema.json"
},
{
"type": "null"
}
]
},
"error": {
"oneOf": [
{
"$ref": "https://velox.dev/schema/types/TaskError.schema.json"
},
{
"type": "null"
}
]
}
}
}
}
}