Repository navigation
ListRunsRequestSchema.limit declares .min(1).max(100) but the boundary enforces only integer-ness — ?limit=0 answers "no runs", ?limit=101 ignores the cap #8054
Description
Activity
Triage: lands in
packages/runtime/src/query-param.ts:121(parseIntegerParamgains optional bounds) with the call site atpackages/runtime/src/domains/automation.ts:737→domain:cli, queued. Re-laned fromdomain:automation— not a lane-table label; the fix package ispackages/runtime. Type intent: Bug (typefield not set —list_issue_types403 for this credential).Serial constraint: #8055 touches the same file (
domains/automation.ts) — same-file hard serial, never the same batch. The suggested shape (pass the schema's own declared bounds rather than re-listing them) matches the #7359 discipline and keeps declared = enforced by construction.Size/model suggestion: S mechanical,
sonnet— correctness is judged by the route's own table of five measured cases.
Generated by Claude Code
Serial block cleared — claimed and dispatched.
domain:cliPM seat (#6024), sessionsession_01B3Kurx8qufrDzNjk4rag7V, branchclaude/issue-8054-list-runs-limit-bounds.The triage seat's serial constraint is discharged: #8125 (#8055) and #8177 (#8123) have both merged, so
packages/runtime/src/domains/automation.tshas no other writer. This card is first in the remaining chain; #7968 follows it on the same file.Taking the triage note's suggested shape as ruled, because it is the point rather than a detail: pass the schema's own declared bounds through to
parseIntegerParamrather than re-listing them at the call site. Re-listing(1, 100)as literals would make the boundary correct today and re-open the identical gap the first timeListRunsRequestSchemachanges its.min()/.max()— declared and enforced would be two facts that happen to agree, which is exactly the state this card is closing. Threading the declaration makesdeclared == enforcedtrue by construction (#7359).Pins required, both directions of the boundary — the card measured them, so they are not hypothetical:
?limit=0must stop answering "no runs",?limit=101must actually be capped at 100 (assert the observed row count or the effective limit, not merely that the request succeeded), and the in-range values must be unaffected.⛔ Not licensed: changing the declared bounds themselves, altering
parseIntegerParam's behaviour for callers that pass no bounds, or touching #7968's/runs/:runId/screengating in the same file.
Generated by Claude Code
os-dev-report
{ "issue": 8054, "status": "done", "branch": "claude/issue-8054-list-runs-limit-bounds", "pr": "https://github.com/objectstack-ai/objectstack/pull/8204", "premise_still_valid": true, "summary": "parseIntegerParam (packages/runtime/src/query-param.ts) gains an optional third `bounds: { min?, max? }` argument, opt-in per call site so existing callers (notifications.ts) are byte-for-byte unaffected. The one call site with a declared range, GET /automation/:name/runs in packages/runtime/src/domains/automation.ts, threads ListRunsRequestSchema.shape.limit.unwrap()'s own minValue/maxValue through rather than re-listing (1, 100) as literals -- the schema's bounds were directly readable at the call site (confirmed via a standalone tsx probe: minValue===1, maxValue===100, both official public Zod v4 ZodNumber accessors). Out-of-range values are refused (400 VALIDATION_FAILED, ADR-0112) with the ADR-0114 min_value/max_value field code, matching the existing refuse-not-clamp precedent in packages/objectql/src/validation/record-validator.ts.", "tests": "pnpm --filter @objectstack/runtime exec vitest run src/domains/automation-runs-query-validation.test.ts: 47 passed (47), including 4 new #8054 pins asserting the full ADR-0112+ADR-0114 envelope for ?limit=0/-5/101/1000 and listRuns never called. Full package suite (pnpm --filter @objectstack/runtime test): 147 test files / 2273 tests passed. pnpm --filter @objectstack/runtime typecheck: clean (tsc --noEmit, exit 0). node scripts/check-nul-bytes.mjs: OK. pnpm check:type-check-debt (after full closure build via turbo): OK -- 'none above its recorded number'; @objectstack/runtime (ceiling 227) never mentioned in the output (no regression, not lowered). Reverse verification: reverted query-param.ts + automation.ts to pre-fix content (git checkout HEAD~1), all four new #8054 cases failed with the exact expected reason -- e.g. '{\"limit\":\"0\"} was accepted (answered {\"status\":200,...}) instead of refused: expected undefined to be defined' -- all 43 other cases stayed green; restored the fix and git diff --stat HEAD came back empty (byte-identical).", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 28, 2026
The same declared-vs-enforced family as #7359, on the sibling parameter of the same route, left behind when
statuswas fixed.Symptom
GET /api/v1/automation/:name/runsagainst a flow with 10 runs:?limit=0?limit=-5?limit=101?limit=abcVALIDATION_FAILED— "Invalid `limit` query parameter — expected a whole number, received "abc""?limit=1.5So the boundary refuses the wrong type but accepts anything outside the declared range. Reproduced 2×, identical both passes.
The
?limit=0arm is the one that bites: a caller paging with a computed limit that reaches 0 is told the flow has no runs — a confidently wrong answer of exactly the shape #7359 was filed for.Root cause
packages/runtime/src/query-param.ts:121parseIntegerParamchecksNumber.isIntegerand returns the value; it has no min/max arm — unlike its siblingparseEnumParam, which does read the closed set.packages/runtime/src/domains/automation.ts:737calls it bare, soListRunsRequestSchema's declared(1, 100)never reaches the boundary.Suggested shape
Give
parseIntegerParamoptional bounds and passListRunsRequestSchema's own at the call site, so the declared and enforced ranges cannot drift — the discipline #7359's fix applied tostatusby readingExecutionStatus.optionsrather than re-listing them.Severity
P3 — no corruption, no leak. A wrong-but-confident answer and an unenforced result-set cap.
Source
Found by the platform checklist retest of
automation.flow-runs-page-test-trigger(framework279ee48a), while verifying #7359's fix. Held no clause of that item.