diff --git a/docs/audits/2026-06-dead-surface-disposition-plan.md b/docs/audits/2026-06-dead-surface-disposition-plan.md index 017a57a578..e5a0a29cfc 100644 --- a/docs/audits/2026-06-dead-surface-disposition-plan.md +++ b/docs/audits/2026-06-dead-surface-disposition-plan.md @@ -59,6 +59,8 @@ No security implication; no roadmap; removing shrinks false surface. Grouped for **field (≈24)** — enhanced-type display config + redundant flags: `theme` / `lineNumbers` (code: only `language` live); `allowHalf` / `maxRating` (rating: `max` live); `displayMap` / `allowGeocoding` / `addressFormat`; `colorFormat` / `allowAlpha` / `presetColors`; `showValue` / `marks` (slider: min/max/step live); `barcodeFormat` / `qrErrorCorrection` / `displayValue` / `allowScanning`; `inlineTitle` / `inlineColumns` / `inlineAmountField` / `relatedList` / `relatedListTitle` / `relatedListColumns` (master-detail explicit overrides — auto-derivation works); `searchable` / `index` / `externalId` (field-level — superseded by object/dataset-level); `cached` / `dependencies` / `trackFeedHistory` / `caseSensitive`. +**✅ Ledgered 2026-08-08 — `field.step` (slider) is UI-ONLY and deliberately unenforced (#6514).** Not a prune row: it is an entry for a key that **stays declared**, added here because the paragraph above is where the next reader looks. The parenthetical "(slider: min/max/step live)" is a *renderer* verdict and had been read as an enforcement one; the two split. **Renderer-live:** objectui `packages/fields/src/widgets/SliderField.tsx:14` reads `field.step ?? 1` and passes it to the Slider, and `packages/spec/liveness/field.json` ledgers `step` as `live` on exactly that evidence — so it is *not* a prune candidate and never joins the pruned `showValue` / `marks` beside it. **Not a value constraint:** the numeric branch of `packages/objectql/src/validation/record-validator.ts` enforces `min`/`max` for `slider` and reads `step` nowhere; an off-grid stored value is accepted. Maintainer ruling 2026-08-08 — **ledger, do NOT enforce** (the ADR-0049 "ledger" half): the settings-side ruling (#6199 / PR #6501, which enforced that schema's `step`) does not transfer, because its hook was the settings schema's own "numeric bounds and step" comment grouping `step` with `min`/`max`, absent from this declaration ("Step increment for slider (default: 1)"); and enforcing a grid would create a stored-data hazard for zero measured demand — `record-validator` judges **updates to existing rows**, so already-stored off-grid values would start failing validation on their next edit. The only declaration in the repo is the showcase's `f_slider` (`min: 0, max: 100, step: 5`). Should grid enforcement gain real user pull, it returns as a feature request in PR #6501's shape: anchor at `min + k * step` (falling back to 0 when no `min` is declared) plus an epsilon-tolerant comparison. Mirrored as a comment beside the declaration in `packages/spec/src/data/field.zod.ts`. + **flow (5)**: `description` / `template` (no reader); `active` (deprecated, redundant with `status`); `nodes.outputSchema` (never validated); `nodes.boundaryConfig` (drives the dead BPMN `boundary_event`); `errorHandling.fallbackNodeId` (engine uses per-node fault edges). *Special:* `flow.status` — currently doesn't gate (engine uses an in-memory `flowEnabled` map); **WIRE** `status` → the enable map, or accept it's display-only and prune. Product call. **tool (5)**: `category` / `requiresConfirmation` / `permissions` / `active` / `builtIn` — cosmetic on a write-only projection. Prune. *(The deeper question — make `tool` metadata authoritative vs stop projecting a schema that implies authorability — is a design decision, not a prune; track separately.)* diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index ede05b4e63..40569d3d19 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -136,7 +136,8 @@ }, "step": { "status": "live", - "note": "slider — SliderField.tsx (min/max/step live)." + "verifiedAt": "2026-08-08", + "note": "CAVEAT — RENDERER-only: objectui SliderField.tsx:14 reads `field.step ?? 1`; the write path never validates the grid (record-validator.ts's numeric branch enforces min/max for slider and reads step nowhere). Ruled UI-only and deliberately unenforced 2026-08-08 (#6514) — see docs/audits/2026-06-dead-surface-disposition-plan.md (P2 field) and the comment beside the declaration in src/data/field.zod.ts." }, "group": { "status": "live", diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index 39b08f27ea..40912bd57b 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -713,6 +713,21 @@ export const FieldSchema = lazySchema(() => strictObject({ // addressFormat, color colorFormat/allowAlpha/presetColors, slider showValue/marks, // barcode/qr barcodeFormat/qrErrorCorrection/displayValue/allowScanning. language: z.string().optional().describe('Programming language for syntax highlighting (e.g., javascript, python, sql)'), + // `step` is the slider's **UI increment** and deliberately NOT a stored-value constraint — + // ADR-0049's "ledger" half, ruled 2026-08-08 (#6514). Note it is renderer-LIVE, not dead, + // which is why it is NOT in the pruned list above and never joins it: objectui's + // `packages/fields/src/widgets/SliderField.tsx:14` reads it (`field.step ?? 1`) and hands it + // to the Slider, and `packages/spec/liveness/field.json` ledgers it `live` on that evidence. + // What it does not do is BIND the written value: the numeric branch of + // `packages/objectql/src/validation/record-validator.ts` enforces `min`/`max` for `slider` + // and reads `step` nowhere. The settings-side ruling (#6199 / PR #6501, which DID enforce a + // grid) does not transfer: its hook was that schema's own "numeric bounds and step" comment + // grouping `step` with `min`/`max`, which this declaration does not share — and enforcing a + // grid here would make already-stored off-grid values start failing on their next edit, + // because record-validator judges updates to existing rows. Should grid enforcement ever + // gain real user pull it returns as a feature request in PR #6501's shape: anchor at + // `min + k * step` (falling back to 0 when no `min` is declared), epsilon-tolerant + // comparison. See docs/audits/2026-06-dead-surface-disposition-plan.md (P2 field prune). step: z.number().optional().describe('Step increment for slider (default: 1)'), // Currency field config