You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implementation ready in PR #222; awaiting human merge.
Problem
The saved-query Spec editor now has JSON parsing, continuous diagnostics, atomic Save, and an app-owned path-validator registry from #212. The current validation model is intentionally small:
Every implemented panel feature must update the canonical schema in the same PR that adds or changes its Spec contract.
Required existing-panel baseline
The canonical schema must fully describe every panel arm already implemented by the application. Future/planned panel contracts may also be present, but tooling must be able to distinguish them from renderers available in the current build.
Every panel.cfg branch must carry:
title human-facing panel name
description concise authoring documentation
x-altinity-status implemented | planned
x-altinity-snippet schema-owned starter structure
Completion offers implemented panel types by default. A planned branch is documentation and forward-compatible validation only until its renderer is registered.
There is no separate panel.name field:
spec.name panel/tile title
panel.fieldConfig.columns.<column>.displayName rendered field label
panel.cfg.type visualization type
Chart family: bar | hbar | line | area | pie
The existing chart family keeps its current zero-based result-column index contract:
measure indexes not conflicting with X/series where the renderer forbids it;
saved panel.key mismatch and role re-derivation.
Table: table
{
"type": "table"
}
The table branch has no required cfg details. Sort order, widths, and other current table state remain surface-local unless a later Spec issue defines persistent fields.
time, msg, and level are optional exact result-column names. They use the shared columnName definition and its resultColumns completion source.
Static schema validates that supplied roles are non-empty strings. Result-aware validation checks that columns exist. time and msg are load-bearing; a missing level degrades to no level coloring, matching the existing renderer.
Only type is required. content is optional, defaults to "" for documentation/completion, and must be a string when present. This matches the existing runtime normalization of missing content to an empty string.
The schema validates the JSON field, not Markdown syntax. The safe Markdown parser/renderer remains authoritative for the supported subset and injection safety.
Existing-panel validation matrix
Panel branch
Static JSON Schema
Result/context validator
Completion source
Chart family
type, index shapes, unique/non-empty y, Pie one-measure limit
bounds, ClickHouse type suitability, role conflicts, panel.key mismatch
resultColumnIndexes
Table
branch/discriminator only
none currently
schema title/docs/snippet
Logs
optional non-empty role names
result-column resolution and log-shape viability
resultColumns
Text/Markdown
optional string content
safe Markdown rendering remains defensive
schema property/snippet
Unknown future type
non-empty string plus extension preservation
unsupported-type diagnostic and fallback
not offered as an implemented type
Extension-preservation rule
Saved-query v2 requires unknown fields to survive all read/write paths. The schema must therefore validate known fields without rejecting extension data.
Rules:
the root Spec allows unknown properties;
panel, dashboard, fieldConfig, and feature objects allow unknown properties unless a future version explicitly changes that contract;
known fields with an invalid value are errors;
unknown fields are accepted and preserved;
unknown panel.cfg.type values remain storable for forward compatibility, but are not considered renderable by the current build.
Unknown-type handling needs two layers:
schema storage validation accepts an object with a non-empty string type;
the runtime panel registry reports that the type is unsupported and applies its documented fallback behavior.
The discriminated union must include an explicit forward-compatible fallback branch rather than relying on a failing oneOf. For example:
Required-property errors point to the missing child path when possible:
['panel','cfg','type']
not only to the containing object:
['panel','cfg']
Keys containing dots remain one path segment.
Schema path resolution
schemaAtPath() and propertiesAtPath() are read-only schema introspection APIs for completion and documentation.
They must resolve the subset used by the canonical schema:
$ref
allOf
oneOf
anyOf
if / then / else
properties
patternProperties
additionalProperties
items
prefixItems
const
enum
default
examples
For a discriminated oneOf, the current Spec value selects the branch:
{
"type": "candlestick"
}
must resolve only the candlestick branch rather than merging unrelated panel fields.
When the discriminator is absent or incomplete, the service may return multiple candidate branches, but it must expose the common properties and the discriminator first.
The service is pure:
no DOM;
no CodeMirror imports;
no application state;
no fetch;
no SQL execution.
Feature/runtime validators
JSON Schema handles static document correctness. It cannot validate facts that depend on query results, ClickHouse types, or application state.
Retain a feature-validator registry, but make it part of one validation service instead of a parallel ad hoc model.
Invalid JSON still skips all semantic validation and remains editable.
2. Atomic Save
Linked Save must synchronously rerun schema and feature validation before persistence.
A stale debounced editor result must never authorize Save.
Any blocking error persists neither SQL nor Spec.
3. File import, Replace, and Append
Validate each supported query after v1-to-v2 upgrade and before mutating the Library.
Behavior:
unsupported envelope or Spec versions retain their current explicit rejection;
schema-invalid supported Specs are rejected with query id/index plus the first diagnostic path;
the operation is atomic: invalid input does not partially replace or append entries;
unknown extension fields remain accepted.
Example message:
Query "latency-kpi": panel.fieldConfig.columns.latency.decimals must be an integer.
4. External Spec writers
Library pencil, favorite toggle, Panel controls, and future dashboard controls already patch the canonical Spec.
Their shared patch helper must:
clone the complete Spec;
apply the known patch;
run schema validation;
persist only when the patched result is valid;
preserve unrelated and unknown fields.
Existing #212 invalid-JSON conflict handling remains authoritative for open drafts.
5. Runtime panel and dashboard consumers
Panel and dashboard code can use static schema validation instead of repeating primitive type and enum checks.
Examples:
panelCfgValid() retains result-shape checks but delegates static cfg shape to the schema service;
dashboard role partitioning uses schema-normalized role values;
renderers receive concise diagnostics rather than raw validator-library errors.
Do not remove renderer-level defensive checks. Imported storage can be corrupted, older builds may have written unsupported values, and runtime result data remains untrusted.
6. Repository examples and generated Libraries
Add a test/helper that validates:
every checked-in example Library;
generated Library output;
Spec examples used in documentation;
test fixtures claiming to be valid.
This prevents documentation and sample files from drifting from the actual application contract.
The schema-service issue only preserves and exposes these annotations. The dependent CodeMirror issue interprets them. x-altinity-status distinguishes implemented branches from planned design contracts so completion does not advertise unavailable panels.
For chart index fields, the schema uses resultColumnIndexes; for Logs and other name-based roles it uses resultColumns.
Normalization
Validation and normalization remain separate operations.
Schema validation must not silently rewrite the Spec.
Continue to normalize only explicitly settled fields during successful Save, for example:
trim name
trim/remove blank description
normalize implemented enum aliases only when a migration explicitly defines them
Do not apply JSON Schema default values by mutating the document. Defaults exist for documentation, completion, and runtime read helpers.
Implementation ready in PR #222; awaiting human merge.
Problem
The saved-query Spec editor now has JSON parsing, continuous diagnostics, atomic Save, and an app-owned path-validator registry from #212. The current validation model is intentionally small:
That was sufficient to validate the first core fields, but it is not a scalable source of truth for the growing Spec surface:
panel.fieldConfig.columnsmetadata;Hand-written validators alone do not describe:
panel.cfgbranches;Without one canonical schema, editor diagnostics, Save validation, import validation, runtime panel checks, examples, and completion will drift.
Goal
Add a pure, reusable Spec schema service backed by a canonical JSON Schema Draft 2020-12 document for
query.spec.The service must provide:
This issue does not implement CodeMirror completion. Completion is a dependent issue and consumes the schema service defined here.
Current base
This issue builds on:
{id, sql, specVersion, spec};src/core/spec-draft.jsparsing and validator registry;src/editor/spec-editor.jsdiagnostic range mapping.The user-editable document remains only:
The schema must not expose or validate the surrounding saved-query fields:
Canonical schema
Add a machine-readable schema:
Required header:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://altinity.com/schemas/altinity-sql-browser/query-spec-v1.schema.json", "title": "Altinity SQL Browser saved-query Spec v1", "type": "object" }The schema is the source of truth for known static structure. It must cover at least the fields implemented when this issue lands:
Panel branches use a discriminator-like
typefield withconstvalues:{ "oneOf": [ { "properties": { "type": { "const": "table" } }, "required": ["type"] }, { "properties": { "type": { "const": "logs" }, "time": { "$ref": "#/$defs/columnName" }, "msg": { "$ref": "#/$defs/columnName" }, "level": { "$ref": "#/$defs/columnName" } }, "required": ["type"] } ], "x-altinity-discriminator": "type" }Every implemented panel feature must update the canonical schema in the same PR that adds or changes its Spec contract.
Required existing-panel baseline
The canonical schema must fully describe every panel arm already implemented by the application. Future/planned panel contracts may also be present, but tooling must be able to distinguish them from renderers available in the current build.
Every
panel.cfgbranch must carry:Completion offers
implementedpanel types by default. A planned branch is documentation and forward-compatible validation only until its renderer is registered.There is no separate
panel.namefield:Chart family:
bar | hbar | line | area | pieThe existing chart family keeps its current zero-based result-column index contract:
{ "type": "line", "x": 0, "y": [1, 2], "series": 3 }Static schema rules:
type,x, andyare required;xis a non-negative integer;yis a non-empty array of unique non-negative integers;seriesis a non-negative integer ornull;y.maxItems = 1;x, eachyitem, and non-nullseriescarry:{ "x-altinity-completion": { "source": "resultColumnIndexes" } }The completion source displays index, name, and ClickHouse type while inserting only the integer index.
Result-aware feature validation remains responsible for:
series !== x;panel.keymismatch and role re-derivation.Table:
table{ "type": "table" }The table branch has no required cfg details. Sort order, widths, and other current table state remain surface-local unless a later Spec issue defines persistent fields.
Logs:
logs{ "type": "logs", "time": "event_time", "msg": "message", "level": "level" }time,msg, andlevelare optional exact result-column names. They use the sharedcolumnNamedefinition and itsresultColumnscompletion source.Static schema validates that supplied roles are non-empty strings. Result-aware validation checks that columns exist.
timeandmsgare load-bearing; a missingleveldegrades to no level coloring, matching the existing renderer.Markdown text:
text{ "type": "text", "content": "# Heading\n\nMarkdown content." }Only
typeis required.contentis optional, defaults to""for documentation/completion, and must be a string when present. This matches the existing runtime normalization of missing content to an empty string.The schema validates the JSON field, not Markdown syntax. The safe Markdown parser/renderer remains authoritative for the supported subset and injection safety.
Existing-panel validation matrix
type, index shapes, unique/non-emptyy, Pie one-measure limitpanel.keymismatchresultColumnIndexesresultColumnscontentExtension-preservation rule
Saved-query v2 requires unknown fields to survive all read/write paths. The schema must therefore validate known fields without rejecting extension data.
Rules:
panel,dashboard,fieldConfig, and feature objects allow unknown properties unless a future version explicitly changes that contract;panel.cfg.typevalues remain storable for forward compatibility, but are not considered renderable by the current build.Unknown-type handling needs two layers:
type;The discriminated union must include an explicit forward-compatible fallback branch rather than relying on a failing
oneOf. For example:{ "allOf": [ { "required": ["type"], "properties": { "type": { "type": "string", "minLength": 1 } } }, { "oneOf": [ { "properties": { "type": { "const": "table" } } }, { "properties": { "type": { "const": "logs" } } }, { "properties": { "type": { "not": { "enum": ["table", "logs"] } } } } ] } ] }The real enum contains every implemented type. The fallback matches only unknown types, so known branches remain unambiguous.
Do not use
additionalProperties: falseas a typo detector in extensible namespaces.Build-time compilation
Use a mature Draft 2020-12 validator rather than implementing JSON Schema semantics by hand.
Recommended implementation:
ajvas a dev dependency;Suggested files:
The generator must:
x-altinity-*annotation keywords so strict schema checking remains enabled;$refvalues or an invalid schema;Add a command such as:
{ "scripts": { "generate:spec-schema": "node build/compile-spec-schema.mjs" } }npm testandnpm run buildmust fail when the generated validator is stale relative to the canonical schema.Equivalent build-time compilation is acceptable, provided the production artifact has no general JSON Schema validator dependency.
Pure schema service
Add:
Suggested interface:
validate(value)Returns normalized diagnostics:
Schema-validation diagnostics are blocking errors unless a specific schema annotation says otherwise.
Stable code mapping:
Do not expose Ajv-specific error object shapes to the rest of the application.
Diagnostic paths
Use the exact path-array model already established by #212.
Examples:
Required-property errors point to the missing child path when possible:
not only to the containing object:
Keys containing dots remain one path segment.
Schema path resolution
schemaAtPath()andpropertiesAtPath()are read-only schema introspection APIs for completion and documentation.They must resolve the subset used by the canonical schema:
For a discriminated
oneOf, the current Spec value selects the branch:{ "type": "candlestick" }must resolve only the candlestick branch rather than merging unrelated panel fields.
When the discriminator is absent or incomplete, the service may return multiple candidate branches, but it must expose the common properties and the discriminator first.
The service is pure:
Feature/runtime validators
JSON Schema handles static document correctness. It cannot validate facts that depend on query results, ClickHouse types, or application state.
Retain a feature-validator registry, but make it part of one validation service instead of a parallel ad hoc model.
Suggested interface:
validate()runs:Feature validator context may include:
Examples of rules that remain feature/runtime validation:
value;min >= maxwhen bounds come from runtime tuple members;low > high;lower > upper;FORMATclause conflicts with a panel-owned result format.The canonical schema should describe the static shape of these configurations, but must not pretend to validate result-dependent correctness.
Consumers
1. Spec editor diagnostics
Replace
CORE_SPEC_VALIDATORSas the primary static validator with the schema validation service.The editor evaluation order remains:
Invalid JSON still skips all semantic validation and remains editable.
2. Atomic Save
Linked Save must synchronously rerun schema and feature validation before persistence.
A stale debounced editor result must never authorize Save.
Any blocking error persists neither SQL nor Spec.
3. File import, Replace, and Append
Validate each supported query after v1-to-v2 upgrade and before mutating the Library.
Behavior:
Example message:
4. External Spec writers
Library pencil, favorite toggle, Panel controls, and future dashboard controls already patch the canonical Spec.
Their shared patch helper must:
Existing #212 invalid-JSON conflict handling remains authoritative for open drafts.
5. Runtime panel and dashboard consumers
Panel and dashboard code can use static schema validation instead of repeating primitive type and enum checks.
Examples:
panelCfgValid()retains result-shape checks but delegates static cfg shape to the schema service;Do not remove renderer-level defensive checks. Imported storage can be corrupted, older builds may have written unsupported values, and runtime result data remains untrusted.
6. Repository examples and generated Libraries
Add a test/helper that validates:
This prevents documentation and sample files from drifting from the actual application contract.
Schema annotations for tooling
The validator treats these as annotations only:
Example:
{ "$defs": { "columnName": { "type": "string", "minLength": 1, "description": "Exact top-level ClickHouse result-column name.", "x-altinity-completion": { "source": "resultColumns" } } } }The schema-service issue only preserves and exposes these annotations. The dependent CodeMirror issue interprets them.
x-altinity-statusdistinguishes implemented branches from planned design contracts so completion does not advertise unavailable panels.For chart index fields, the schema uses
resultColumnIndexes; for Logs and other name-based roles it usesresultColumns.Normalization
Validation and normalization remain separate operations.
Schema validation must not silently rewrite the Spec.
Continue to normalize only explicitly settled fields during successful Save, for example:
Do not apply JSON Schema
defaultvalues by mutating the document. Defaults exist for documentation, completion, and runtime read helpers.Files
Expected changes:
Equivalent factoring is acceptable.
Tests
Canonical schema
$refresolves;$idand Spec version remain stable;Static validation
contentand rejects non-stringcontent;ymeasure;oneOffailures into concise diagnostics rather than exposing every branch error.Schema lookup
$refvalues;x-altinity-status, snippets, and otherx-altinity-*annotations;resultColumnIndexesannotations on chart roles andresultColumnsannotations on Logs roles.Integration
panelCfgValid()or its successor delegates static chart/logs/text shape checks to the schema service without removing renderer defenses;Regression
Acceptance criteria
query.specis checked in.bar,hbar,line,area,pie,table,logs,text) have explicit branches, titles, descriptions, snippets, and static constraints.contentis optional with a documented empty-string default; Pie accepts exactly one measure.Non-goals
$refloading.id, SQL,specVersion, export envelopes, compatibility mirrors, or runtime state.