proto: answer CORE's freeze-blockers before 1.0.0 lands
Three corrections into 1.0.0, all of which would be major bumps once the contract has landed. It has not: main still carries 1.0.0-draft, so these are corrections to an unpublished version rather than changes to a released one. ADR 0010 records that and the reasoning behind each. B1 — TaskError.code was a bare integer, and the integer space in the contract is JSON-RPC's, which is a different thing; TaskError's own description said so while typing its code as one. Freeze TaskErrorCode: a string enum mirroring vdm::Error by name and in order, all 27 failure values, verified against core/include/vdm/util/error.hpp mechanically. ErrorCode says why a call failed; TaskErrorCode says why a download failed, and a download fails while every RPC succeeds. Adds TaskError.cause so max_retries_exhausted names what kept failing. B2 — TaskSummary.segments is now explicitly the effective count in use right now, after the per-host cap and the non-resumable demotion to 1. DownloadSpec.segments and download.update's patch say they are the requested value. B3 — Segment.endByte's "minimum: 0" contradicted the description's own empty-range encoding of startByte - 1, which is -1 for the first segment of every download. Empty ranges are no longer representable and are not needed. The range stays CLOSED and INCLUSIVE, matching the HTTP Range header the two fields are copied into verbatim, and that is now stated in the schema, the README, an ADR, a fixture assertion and a conformance check. CORE asked for half-open and gets a written notice rather than a silent schema edit. Segment state spells 'downloading' as CORE asked, not 'receiving'. check_contract.py now enforces segment contiguity, coverage of exactly [0, sizeBytes-1], downloadedBytes within the range size, and the entry count matching TaskSummary.segments. The download.get fixture claimed 8 segments while carrying 2; it now carries 8 contiguous ones covering the whole file. contracts/proto-answers-m1.md answers every item in core/docs/proto-requests-m1.md, including the ones not being landed now: B2a and F2 accepted as follow-ups, F1 answered with the notify path for M1, F3 already frozen as a Checksum object rather than a string, and D1 left for DAEMON to draft as the three-way ADR it is. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
This commit is contained in:
@@ -5,21 +5,110 @@
|
||||
"description": "Everything needed to create one task. Shared by download.add and each item of download.addBatch, so the two can never drift apart.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["url"],
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"url": { "type": "string", "format": "uri" },
|
||||
"headers": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/Headers.schema.json" }, { "type": "null" }] },
|
||||
"cookies": { "type": ["array", "null"], "items": { "$ref": "https://velox.dev/schema/types/Cookie.schema.json" } },
|
||||
"referrer": { "type": ["string", "null"] },
|
||||
"userAgent": { "type": ["string", "null"] },
|
||||
"filename": { "type": ["string", "null"], "maxLength": 255, "description": "Overrides the name derived from Content-Disposition or the URL." },
|
||||
"saveDir": { "type": ["string", "null"], "description": "Canonicalized and checked against the allowed roots before any write. -32011 if it fails." },
|
||||
"categoryId": { "type": ["string", "null"], "description": "null means the rules engine picks one." },
|
||||
"queueId": { "type": ["string", "null"], "description": "Required when startMode is 'queue'." },
|
||||
"segments": { "type": ["integer", "null"], "minimum": 1, "maximum": 32 },
|
||||
"bufferBytes": { "type": ["integer", "null"], "minimum": 4096, "maximum": 8388608 },
|
||||
"startMode": { "$ref": "https://velox.dev/schema/types/StartMode.schema.json" },
|
||||
"description": { "type": ["string", "null"], "maxLength": 1024 },
|
||||
"checksum": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/Checksum.schema.json" }, { "type": "null" }] }
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
},
|
||||
"headers": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://velox.dev/schema/types/Headers.schema.json"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cookies": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "https://velox.dev/schema/types/Cookie.schema.json"
|
||||
}
|
||||
},
|
||||
"referrer": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"userAgent": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"filename": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 255,
|
||||
"description": "Overrides the name derived from Content-Disposition or the URL."
|
||||
},
|
||||
"saveDir": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Canonicalized and checked against the allowed roots before any write. -32011 if it fails."
|
||||
},
|
||||
"categoryId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "null means the rules engine picks one."
|
||||
},
|
||||
"queueId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Required when startMode is 'queue'."
|
||||
},
|
||||
"segments": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 1,
|
||||
"maximum": 32,
|
||||
"description": "The REQUESTED connection count. An upper bound, not a promise: the daemon lowers it to the per-host cap, and to 1 when the source turns out not to be resumable. What is actually in use comes back as TaskSummary.segments. null means use connection.maxSegmentsPerDownload."
|
||||
},
|
||||
"bufferBytes": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 4096,
|
||||
"maximum": 8388608
|
||||
},
|
||||
"startMode": {
|
||||
"$ref": "https://velox.dev/schema/types/StartMode.schema.json"
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 1024
|
||||
},
|
||||
"checksum": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://velox.dev/schema/types/Checksum.schema.json"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://velox.dev/schema/types/Segment.schema.json",
|
||||
"title": "Segment",
|
||||
"description": "One byte range being fetched by one connection. This is the deepest the contract ever exposes the engine: the GUI draws a bar per segment and never learns what a segment steal is.",
|
||||
"description": "One byte range being fetched by one connection. This is the deepest the contract ever exposes the engine: the GUI draws a bar per segment and is never told what a segment steal is.\n\nRANGE CONVENTION — READ THIS BEFORE IMPLEMENTING. The range is CLOSED and INCLUSIVE on both ends: [startByte, endByte]. The segment covers endByte - startByte + 1 bytes, and endByte is the index of the LAST byte in the range, not one past it. This deliberately matches the HTTP Range header the engine actually sends ('Range: bytes=<startByte>-<endByte>' is a byte-for-byte copy of these two fields, and RFC 9110 ranges are inclusive), so no arithmetic happens between the wire and the socket and there is nowhere for an off-by-one to hide. CORE asked for half-open [start, end); PROTO chose inclusive for that reason and this note exists so nobody discovers the difference at integration. A segment always covers at least one byte: endByte >= startByte always holds. An empty range is not representable and is not needed — a zero-length download carries an empty segmentDetail array, and a segment that has donated its remainder to a steal keeps the bytes it already wrote.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["index", "startByte", "endByte", "downloadedBytes", "state"],
|
||||
"properties": {
|
||||
"index": { "type": "integer", "minimum": 0, "maximum": 31 },
|
||||
"startByte": { "type": "integer", "minimum": 0 },
|
||||
"endByte": { "type": "integer", "minimum": 0, "description": "Inclusive. Equal to startByte - 1 for an empty segment." },
|
||||
"downloadedBytes": { "type": "integer", "minimum": 0 },
|
||||
"index": { "type": "integer", "minimum": 0, "maximum": 31, "description": "Position in TaskDetail.segmentDetail. Spelled 'index' here and in event.task.progress; there is no 'i' spelling anywhere in the contract." },
|
||||
"startByte": { "type": "integer", "minimum": 0, "description": "Absolute offset of the first byte of the range. Inclusive." },
|
||||
"endByte": { "type": "integer", "minimum": 0, "description": "Absolute offset of the LAST byte of the range. Inclusive — this is not one-past-the-end. Always >= startByte." },
|
||||
"downloadedBytes": { "type": "integer", "minimum": 0, "description": "Bytes written for this range so far, out of endByte - startByte + 1." },
|
||||
"speedBps": { "type": "integer", "minimum": 0 },
|
||||
"state": { "type": "string", "enum": ["pending", "connecting", "receiving", "stalled", "complete", "failed"] },
|
||||
"httpStatus": { "type": ["integer", "null"] }
|
||||
"state": { "type": "string", "enum": ["pending", "connecting", "downloading", "stalled", "complete", "failed"], "description": "'downloading' is spelled as in TaskState, not 'receiving'. 'pending' is a range that has been planned but not yet dialled." },
|
||||
"httpStatus": { "type": ["integer", "null"], "minimum": 100, "maximum": 599, "description": "The status this segment's request got. 206 on a healthy ranged fetch." }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,23 +5,92 @@
|
||||
"description": "Everything TaskSummary carries, plus what only the progress dialog and the File Info dialog need. Returned by download.get; never sent in a list or an event, because it is expensive to build.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["summary", "segmentDetail"],
|
||||
"required": [
|
||||
"summary",
|
||||
"segmentDetail"
|
||||
],
|
||||
"properties": {
|
||||
"summary": { "$ref": "https://velox.dev/schema/types/TaskSummary.schema.json" },
|
||||
"summary": {
|
||||
"$ref": "https://velox.dev/schema/types/TaskSummary.schema.json"
|
||||
},
|
||||
"segmentDetail": {
|
||||
"type": "array",
|
||||
"maxItems": 32,
|
||||
"items": { "$ref": "https://velox.dev/schema/types/Segment.schema.json" }
|
||||
"items": {
|
||||
"$ref": "https://velox.dev/schema/types/Segment.schema.json"
|
||||
},
|
||||
"description": "Exactly TaskSummary.segments entries, in index order, covering [0, sizeBytes) with no gaps and no overlaps. Empty for a zero-length download, and empty before the task has been segmented."
|
||||
},
|
||||
"headers": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/Headers.schema.json" }, { "type": "null" }] },
|
||||
"referrer": { "type": ["string", "null"] },
|
||||
"userAgent": { "type": ["string", "null"] },
|
||||
"mime": { "type": ["string", "null"] },
|
||||
"bufferBytes": { "type": ["integer", "null"], "minimum": 4096, "maximum": 8388608 },
|
||||
"partPath": { "type": ["string", "null"], "description": "Absolute path of the .veloxpart file while the task is unfinished." },
|
||||
"checksum": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/Checksum.schema.json" }, { "type": "null" }] },
|
||||
"checksumVerified": { "type": ["boolean", "null"], "description": "null until the verifying state has run." },
|
||||
"averageSpeedBps": { "type": ["integer", "null"], "minimum": 0 },
|
||||
"retryCount": { "type": "integer", "minimum": 0 }
|
||||
"headers": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://velox.dev/schema/types/Headers.schema.json"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"referrer": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"userAgent": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"mime": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"bufferBytes": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 4096,
|
||||
"maximum": 8388608
|
||||
},
|
||||
"partPath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "Absolute path of the .veloxpart file while the task is unfinished."
|
||||
},
|
||||
"checksum": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://velox.dev/schema/types/Checksum.schema.json"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"checksumVerified": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
],
|
||||
"description": "null until the verifying state has run."
|
||||
},
|
||||
"averageSpeedBps": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0
|
||||
},
|
||||
"retryCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://velox.dev/schema/types/TaskError.schema.json",
|
||||
"title": "TaskError",
|
||||
"description": "Why a task is in the failed or retry_wait state. Distinct from the JSON-RPC Error, which describes a failed call rather than a failed download.",
|
||||
"description": "Why a task is in the failed or retry_wait state. Distinct from the JSON-RPC Error, which describes a failed call rather than a failed download — the two live in different code spaces on purpose, and `code` here is a TaskErrorCode string, never a JSON-RPC integer.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["code", "message", "retryable"],
|
||||
"properties": {
|
||||
"code": { "type": "integer" },
|
||||
"message": { "type": "string" },
|
||||
"httpStatus": { "type": ["integer", "null"] },
|
||||
"retryable": { "type": "boolean" },
|
||||
"code": { "$ref": "https://velox.dev/schema/types/TaskErrorCode.schema.json" },
|
||||
"message": { "type": "string", "description": "Human-readable, safe to show a user. Never carries a credential, a token or a full local path outside the download roots." },
|
||||
"httpStatus": { "type": ["integer", "null"], "minimum": 100, "maximum": 599, "description": "Set for the codes listed in TaskErrorCode's x-carriesHttpStatus, and null otherwise." },
|
||||
"retryable": { "type": "boolean", "description": "Whether the scheduler will pick this task up again on its own. Carried per-occurrence rather than derived from the code, because 'probe_failed' is retryable or not depending on what the probe hit." },
|
||||
"cause": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/TaskErrorCode.schema.json" }, { "type": "null" }], "description": "The underlying failure, for codes that wrap one. max_retries_exhausted sets it to whatever the last attempt actually failed with, so a user learns the reason rather than just that Velox gave up." },
|
||||
"attempt": { "type": ["integer", "null"], "minimum": 0, "description": "How many attempts have been made so far." },
|
||||
"nextRetryAt":{ "type": ["string", "null"], "format": "date-time" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://velox.dev/schema/types/TaskErrorCode.schema.json",
|
||||
"title": "TaskErrorCode",
|
||||
"description": "Why a download failed. This is the WIRE failure taxonomy and it is deliberately NOT the JSON-RPC ErrorCode space: ErrorCode says why a *call* failed, TaskErrorCode says why a *download* failed. A task can fail while every RPC involved succeeded. The values mirror vdm::Error in core/include/vdm/util/error.hpp one-for-one, by name, so DAEMON's projection from the engine taxonomy onto the wire is lossless and the GUI can tell 'the file on the server changed' from 'the checksum did not match'. CORE's 'ok' has no wire spelling: a TaskError only exists when there is a failure. Adding a value here is a minor bump; renaming or removing one is major, and would desynchronise the engine.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"canceled",
|
||||
|
||||
"resolve_failed",
|
||||
"connect_failed",
|
||||
"tls_failed",
|
||||
"connection_reset",
|
||||
"timeout",
|
||||
"too_many_redirects",
|
||||
|
||||
"http_client_error",
|
||||
"http_server_error",
|
||||
"auth_required",
|
||||
"forbidden",
|
||||
"not_found",
|
||||
"range_not_satisfiable",
|
||||
"gone",
|
||||
|
||||
"server_file_changed",
|
||||
"content_length_mismatch",
|
||||
"checksum_mismatch",
|
||||
|
||||
"disk_full",
|
||||
"io_error",
|
||||
"path_rejected",
|
||||
"permission_denied",
|
||||
|
||||
"meta_corrupt",
|
||||
"meta_version_unsupported",
|
||||
|
||||
"probe_failed",
|
||||
"unsupported_url_scheme",
|
||||
|
||||
"max_retries_exhausted",
|
||||
|
||||
"internal"
|
||||
],
|
||||
"x-groups": {
|
||||
"cancellation": ["canceled"],
|
||||
"network": ["resolve_failed", "connect_failed", "tls_failed", "connection_reset", "timeout", "too_many_redirects"],
|
||||
"http": ["http_client_error", "http_server_error", "auth_required", "forbidden", "not_found", "range_not_satisfiable", "gone"],
|
||||
"content": ["server_file_changed", "content_length_mismatch", "checksum_mismatch"],
|
||||
"localIo": ["disk_full", "io_error", "path_rejected", "permission_denied"],
|
||||
"resumeMetadata": ["meta_corrupt", "meta_version_unsupported"],
|
||||
"probe": ["probe_failed", "unsupported_url_scheme"],
|
||||
"retry": ["max_retries_exhausted"],
|
||||
"internal": ["internal"]
|
||||
},
|
||||
"x-carriesHttpStatus": [
|
||||
"http_client_error", "http_server_error", "auth_required", "forbidden", "not_found",
|
||||
"range_not_satisfiable", "gone", "server_file_changed", "probe_failed", "max_retries_exhausted"
|
||||
]
|
||||
}
|
||||
@@ -5,28 +5,134 @@
|
||||
"description": "One row of the main download list. Everything the GUI table needs, and nothing more. TaskDetail is the same shape plus the fields only the progress dialog and File Info dialog need.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["taskId", "filename", "saveDir", "url", "state", "downloadedBytes",
|
||||
"speedBps", "resumable", "segments", "createdAt"],
|
||||
"required": [
|
||||
"taskId",
|
||||
"filename",
|
||||
"saveDir",
|
||||
"url",
|
||||
"state",
|
||||
"downloadedBytes",
|
||||
"speedBps",
|
||||
"resumable",
|
||||
"segments",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"taskId": { "type": "string", "format": "uuid" },
|
||||
"filename": { "type": "string", "maxLength": 255 },
|
||||
"saveDir": { "type": "string", "description": "Absolute, canonicalized, inside an allowed root." },
|
||||
"url": { "type": "string", "format": "uri", "description": "The URL as the user or the extension supplied it." },
|
||||
"effectiveUrl": { "type": ["string", "null"], "format": "uri", "description": "After redirects. null until the first probe succeeds." },
|
||||
"sizeBytes": { "type": ["integer", "null"], "minimum": 0, "description": "null when the server did not report a length." },
|
||||
"downloadedBytes": { "type": "integer", "minimum": 0 },
|
||||
"state": { "$ref": "https://velox.dev/schema/types/TaskState.schema.json" },
|
||||
"speedBps": { "type": "integer", "minimum": 0 },
|
||||
"etaSeconds": { "type": ["integer", "null"], "minimum": 0, "description": "null when the size or the speed is unknown." },
|
||||
"resumable": { "type": "boolean" },
|
||||
"segments": { "type": "integer", "minimum": 1, "maximum": 32, "description": "Connection count. Per-segment detail lives in TaskDetail." },
|
||||
"categoryId": { "type": ["string", "null"] },
|
||||
"queueId": { "type": ["string", "null"] },
|
||||
"queuePosition":{ "type": ["integer", "null"], "minimum": 0, "description": "The Q column." },
|
||||
"description": { "type": ["string", "null"], "maxLength": 1024 },
|
||||
"createdAt": { "type": "string", "format": "date-time" },
|
||||
"lastTryAt": { "type": ["string", "null"], "format": "date-time" },
|
||||
"completedAt": { "type": ["string", "null"], "format": "date-time" },
|
||||
"error": { "oneOf": [{ "$ref": "https://velox.dev/schema/types/TaskError.schema.json" }, { "type": "null" }] }
|
||||
"taskId": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
"saveDir": {
|
||||
"type": "string",
|
||||
"description": "Absolute, canonicalized, inside an allowed root."
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "The URL as the user or the extension supplied it."
|
||||
},
|
||||
"effectiveUrl": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "uri",
|
||||
"description": "After redirects. null until the first probe succeeds."
|
||||
},
|
||||
"sizeBytes": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0,
|
||||
"description": "null when the server did not report a length."
|
||||
},
|
||||
"downloadedBytes": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"state": {
|
||||
"$ref": "https://velox.dev/schema/types/TaskState.schema.json"
|
||||
},
|
||||
"speedBps": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"etaSeconds": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0,
|
||||
"description": "null when the size or the speed is unknown."
|
||||
},
|
||||
"resumable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"segments": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 32,
|
||||
"description": "The EFFECTIVE connection count in use right now \u2014 not the number that was requested. It is what remains after the per-host connection cap has been applied and after the demotion to 1 for a non-resumable source, so a task the user asked for 16 connections on legitimately reports 4, or 1. The GUI displays this value and must not assume it equals what download.add asked for. The requested value lives in DownloadSpec.segments and is not echoed back on this type. TaskDetail.segmentDetail always has exactly this many entries."
|
||||
},
|
||||
"categoryId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"queueId": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"queuePosition": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"minimum": 0,
|
||||
"description": "The Q column."
|
||||
},
|
||||
"description": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"maxLength": 1024
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastTryAt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"completedAt": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"format": "date-time"
|
||||
},
|
||||
"error": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://velox.dev/schema/types/TaskError.schema.json"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user