Repository navigation
Commit 2581efb
fix(service-automation): get_record form descriptor's limit declares minimum 1 (#25441)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuACdPWFJTLDcTrByFCHLzqb)_
Fixes #25361
Clause-②: no
Metadata-protocol: none
## What
The `get_record` node's `limit` is a positive integer in the contract
(`GetRecordConfigSchema.limit`, `z.number().int().positive()`, narrowed
by PR #25357), so every flow save door refuses `0`, a negative or a
fraction. The executor's form descriptor in
`packages/services/service-automation/src/builtin/crud-nodes.ts` still
declared `limit: { type: 'integer', title: 'Limit' }`, so the designer
form offered values the save always refuses.
- The descriptor's `limit` now declares `minimum: 1`, the contract's
floor.
- Landing: the claimed file, `builtin/crud-nodes.ts`. No `packages/spec`
edit, no `engine.ts` edit.
- Derivation was considered: no node descriptor in `builtin/` is derived
from its spec Zod today. Every descriptor is a hand-written literal, and
the bounded siblings (`loop.maxIterations`, `try_catch.retry.*`) declare
`minimum`/`maximum` by hand, so this follows that pattern.
## Pin
`builtin-node-form-zod-ledger.test.ts` gains `get_record.limit: the
form's minimum is the contract's refusal boundary`. It reads the
descriptor's `limit.minimum` (asserts `1`), then parses
`GetRecordConfigSchema` at that boundary: the minimum is accepted, one
below it is refused, and `0` is refused. Neither side can move alone.
## Measurement: other `builtin/` descriptors against their contracts
Every numeric property in `builtin/` descriptors was compared with its
spec Zod at this tree:
| Descriptor key | Descriptor | Contract | Agree |
|:---|:---|:---|:---|
| `get_record.limit` | integer, now `minimum: 1` | `int().positive()` |
yes (this PR) |
| `loop.maxIterations` | integer, `minimum: 1`, `maximum:
LOOP_MAX_ITERATIONS_CEILING` |
`int().min(1).max(LOOP_MAX_ITERATIONS_CEILING)` | yes |
| `try_catch.retry.maxRetries` | integer 0..10 | `int().min(0).max(10)`
| yes |
| `try_catch.retry.backoffMs` / `maxRetryDelayMs` | integer, `minimum:
0` | `int().min(0)` | yes |
| `try_catch.retry.backoffMultiplier` | number, `minimum: 1` | `min(1)`
| yes |
| `http.timeoutMs` | number | `z.number()` (unbounded) | yes |
| `screen.fields[].min` / `max` | number | `z.number()` (unbounded) |
yes |
No other mismatch was found.
## Verification (head `21ecd5680e`)
- `pnpm --filter @objectstack/service-automation typecheck && pnpm
--filter @objectstack/service-automation test`: lock VERDICT
command-exit 0; Test Files 198 passed (198), Tests 2619 passed (2619).
- Ablation (committed fix first, through
`scripts/ablation-replace.mjs`): the anchor `minimum: 1` was removed
(anchor 1 to 0, blob `b7d8ec771a76` to `5c181206f838`). The new pin went
red: `AssertionError: expected undefined to be 1`, 1 failed and 9
passed. The file was restored with blob == HEAD and `git diff HEAD`
empty. The test imports `crud-nodes.js` from `src`, so no `dist/` hop is
involved.
- Derived gate families (`dispatch-gates.mjs --commands`, 67): 66 exit
0. `check:dual-build-cjs-loads` exited 3 with PREREQUISITE NOT MET (no
full-repo `dist/`), so it is NOT MEASURED locally and is left to CI.
## Acceptance notes
- None.
---
_Generated by [Claude
Code](https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8d9e8b9 commit 2581efb
3 files changed
Lines changed: 32 additions & 1 deletion
File tree
- .changeset
- packages/services/service-automation/src/builtin
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
139 | 157 | | |
140 | 158 | | |
141 | 159 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
477 | 477 | | |
478 | 478 | | |
479 | 479 | | |
480 | | - | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
481 | 484 | | |
482 | 485 | | |
483 | 486 | | |
| |||
0 commit comments