From 21ecd5680e82947292252d66b622af0fe8f6884f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 12:53:10 +0000 Subject: [PATCH] fix(service-automation): get_record form descriptor's limit declares minimum 1 The contract refuses a get_record limit below 1 at every flow save door; the designer form descriptor offered 0 and negatives. Declare minimum: 1 and pin the descriptor's floor against the contract's refusal boundary. Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy Co-authored-by: Claude --- .../25361-get-record-limit-form-minimum.md | 10 ++++++++++ .../builtin-node-form-zod-ledger.test.ts | 18 ++++++++++++++++++ .../src/builtin/crud-nodes.ts | 5 ++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/25361-get-record-limit-form-minimum.md diff --git a/.changeset/25361-get-record-limit-form-minimum.md b/.changeset/25361-get-record-limit-form-minimum.md new file mode 100644 index 00000000000..3fa2393cbe3 --- /dev/null +++ b/.changeset/25361-get-record-limit-form-minimum.md @@ -0,0 +1,10 @@ +--- +'@objectstack/service-automation': patch +--- + +fix: the `get_record` designer form no longer offers a `limit` below 1 + +Clause-②: no +Metadata-protocol: none + +The `get_record` node's `limit` is a positive integer, and every flow save door refuses `0`, a negative or a fraction. The executor's form descriptor still declared `limit` as a bare integer, so the designer form offered values the save always refused. The descriptor's `limit` now declares `minimum: 1`, the same floor as the contract. Nothing a flow can store changes; only the form stops offering what the save refuses. diff --git a/packages/services/service-automation/src/builtin/builtin-node-form-zod-ledger.test.ts b/packages/services/service-automation/src/builtin/builtin-node-form-zod-ledger.test.ts index 38474aff37a..903515f7ba4 100644 --- a/packages/services/service-automation/src/builtin/builtin-node-form-zod-ledger.test.ts +++ b/packages/services/service-automation/src/builtin/builtin-node-form-zod-ledger.test.ts @@ -136,6 +136,24 @@ describe('CRUD / screen / map form ↔ Zod reconciliation (#4045)', () => { ).toEqual([]); }); + it('get_record.limit: the form\'s minimum is the contract\'s refusal boundary', () => { + // The contract refuses a `limit` below 1 at every save door; a form whose + // floor sits lower offers values the save always refuses. Pin both sides + // at the boundary the descriptor declares, so neither moves alone. + const limit = formSchema('get_record').properties?.limit as + | { type?: string; minimum?: number } + | undefined; + expect(limit?.type).toBe('integer'); + expect(limit?.minimum).toBe(1); + + const parse = (value: number) => + (GetRecordConfigSchema as unknown as { safeParse(v: unknown): { success: boolean } }) + .safeParse({ objectName: 'account', limit: value }).success; + expect(parse(limit!.minimum!), 'the contract accepts the form\'s minimum').toBe(true); + expect(parse(limit!.minimum! - 1), 'the contract refuses one below the form\'s minimum').toBe(false); + expect(parse(0), 'the contract refuses 0').toBe(false); + }); + describe('assignment: pinned as un-reconcilable, with the reason on record', () => { // `assignment` deliberately has NO entry in builtin-node-config.zod.ts: with // no `assignments` wrapper, the TOP-LEVEL config keys ARE the author's diff --git a/packages/services/service-automation/src/builtin/crud-nodes.ts b/packages/services/service-automation/src/builtin/crud-nodes.ts index 04f9726049a..b7d8ec771a7 100644 --- a/packages/services/service-automation/src/builtin/crud-nodes.ts +++ b/packages/services/service-automation/src/builtin/crud-nodes.ts @@ -477,7 +477,10 @@ export function registerCrudNodes(engine: AutomationEngine, ctx: PluginContext): type: 'array', items: { type: 'string' }, title: 'Fields', description: 'Field projection — only these fields are read (default: all).', }, - limit: { type: 'integer', title: 'Limit' }, + // `minimum: 1` mirrors the contract: `GetRecordConfigSchema.limit` + // is a positive integer, refused below 1 at every flow save + // door, so the form never offers a value the save refuses. + limit: { type: 'integer', minimum: 1, title: 'Limit' }, outputVariable: { type: 'string', title: 'Output variable' }, }, required: ['objectName'],