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:168 — f_slider: Field.slider({ label: 'Slider', min: 0, max: 100, step: 5 })
packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts:46 — f_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
Found while implementing #6199 (settings
stepenforcement, PR #6501). Not in #6199's completion scope — that issue's ruling and file surface arepackages/services/service-settingsonly, and this is a different schema judged by a different validator. Filed for triage.Facts
packages/spec/src/data/field.zod.ts:697declares astepon the data field surface:packages/objectql/src/validation/record-validator.tsenforces that field's numeric window — lines 529-533:…and has no reader for
def.stepanywhere. Grep overpackages/objectql/src/validation/returns nostepread at all (the only textual hits arelock-stepprose andstmt.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:
minandmaxbind, thestepdeclared beside them does not.Why this is a separate issue and not a rider on #6199
Different schema (
field.zod.ts, notsettings-manifest.zod.ts), different validator (record-validator.ts, notSettingsService.validatePatch), different error path. #6199's ruling turned on the settings schema's own "numeric bounds and step" comment groupingstepwithmin/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:168—f_slider: Field.slider({ label: 'Slider', min: 0, max: 100, step: 5 })packages/drivers/driver-sql/src/sql-driver-numeric-fidelity.test.ts:46—f_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-695already carries a comment listing slidershowValue/marksamong keys the runtime reader ignores, pointing atdocs/audits/2026-06-dead-surface-disposition-plan.md(P2 field prune).stepis 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_sliderwithstep: 5on a 0-100 range, and an off-grid value there is a perfectly ordinary number for every downstream consumer. Recording it as afinding(nopm: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 nomin) 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 besideshowValue/marks) rather than silence.Related
step是同族第五个声明但不执行的值约束键 —— 先要一个「它算不算值约束」的判断 #6199 / PR fix(service-settings): enforce the declaredstepgrid on the settings write path and the env path (#6199) #6501 — the settings-sidestep, ruled a value constraint and enforced.min/max/minLength/maxLengthfamily this descends from.Generated by Claude Code