Skip to content

The data-field step (slider) is the settings step's unenforced twin — record-validator.ts reads min/max and never step #6514

Description

@os-project-manager

Found while implementing #6199 (settings step enforcement, PR #6501). Not in #6199's completion scope — that issue's ruling and file surface are packages/services/service-settings only, and this is a different schema judged by a different validator. Filed for triage.

Facts

packages/spec/src/data/field.zod.ts:697 declares a step on the data field surface:

step: z.number().optional().describe('Step increment for slider (default: 1)'),

packages/objectql/src/validation/record-validator.ts enforces that field's numeric window — lines 529-533:

if (def.min !== undefined && n < def.min) {
  return fail('min_value', { min: def.min });
}
...                                     fail('max_value', { max: def.max });

…and has no reader for def.step anywhere. Grep over packages/objectql/src/validation/ returns no step read at all (the only textual hits are lock-step prose and stmt.step() in the sqlite-wasm dialect, unrelated).

So the exact asymmetry #6199 named for the settings specifier exists one surface over, on the data field: min and max bind, the step declared beside them does not.

Why this is a separate issue and not a rider on #6199

Different schema (field.zod.ts, not settings-manifest.zod.ts), different validator (record-validator.ts, not SettingsService.validatePatch), different error path. #6199's ruling turned on the settings schema's own "numeric bounds and step" comment grouping step with min/max; the data-field declaration has a different doc string — "Step increment for slider (default: 1)" — which reads more like a UI increment and does not obviously inherit that ruling. That is a judgement call for triage, not something to assume.

Declaration sites

  • examples/app-showcase/src/data/objects/field-zoo.object.ts:168f_slider: Field.slider({ label: 'Slider', min: 0, max: 100, step: 5 })
  • packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts:46f_slider: { type: 'slider', min: 0, max: 100, step: 5 }
  • packages/spec/src/data/field.test.ts:698 — asserts only that the declaration round-trips (expect(sliderField.step).toBe(5)), not that anything enforces it.

Note field.zod.ts:691-695 already carries a comment listing slider showValue/marks among keys the runtime reader ignores, pointing at docs/audits/2026-06-dead-surface-disposition-plan.md (P2 field prune). step is not in that list, so it is currently neither enforced nor ledgered as deliberately inert — which is the part worth a verdict.

Impact

Observation-class. No user hits this today: the only real declaration is the showcase's f_slider with step: 5 on a 0-100 range, and an off-grid value there is a perfectly ordinary number for every downstream consumer. Recording it as a finding (no pm:queue); severity is triage's call.

If the verdict is "enforce", PR #6501 is the shape to copy — including its anchor convention (min + k * step, anchor 0 when no min) and its epsilon-tolerant comparison, since exact modulo rejects legitimate decimal grids under binary floating point. If the verdict is "UI-only", the fix is the ADR-0049 disposition (retire, or ledger it in the dead-surface plan beside showValue/marks) rather than silence.

Related


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions