Repository navigation
fix(spec)!: a flow get_record node's limit is declared a positive integer, so a fraction, 0 or a negative is refused at every door that parses a flow - #25357
Conversation
Claude-Session: https://claude.ai/code/session_013XF6vcPvtFkNY5BmaMGzcd Co-authored-by: Claude <noreply@anthropic.com>
…rd limit Claude-Session: https://claude.ai/code/session_013XF6vcPvtFkNY5BmaMGzcd Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b1b7e98e31d1236b1d2820138bd487f5204f891f && git checkout b1b7e98e31d1236b1d2820138bd487f5204f891f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5f4a3f088cd7018aef904a9f31ab6b071251733c d972d18be0dd135ba8362c53bbeb9d4612cf5f81 && git checkout -B drift-repro 5f4a3f088cd7018aef904a9f31ab6b071251733c && git merge --no-ff d972d18be0dd135ba8362c53bbeb9d4612cf5f81
node scripts/docs-audit/affected-docs.mjs --json 5f4a3f088cd7018aef904a9f31ab6b071251733c |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT ·
|
Requested by Tesla · project thread
Fixes #25302
Clause-②: no (narrowing)
Metadata-protocol: narrowed
What changes
GetRecordConfigSchema.limit(packages/spec/src/automation/builtin-node-config.zod.ts) goes fromz.number()toz.number().int().positive(). Theget_recordexecutor (packages/services/service-automation/src/builtin/crud-nodes.ts) parses its config against this contract, then callsdata.findwithlimitas written whenlimit > 1, andfindOneotherwise. It does not go throughfindData, so the Data API's page-window normalizer never judges it. Before this changelimit: 2.5reached the driver as its LIMIT, and0,-1or0.5silently read one record.The executor contract is the one judge every flow door shares (
flowNodeConfigRefusals, the value half). Narrowing it refuses the value atnodes.N.config.limitwithnode-config-refused-by-contractatFlowSchema.parse,defineStack(STACK_SCHEMA_INVALID, 422),objectstack validate/compile, the registeredflowtype schema the metadata save door validates against,AutomationEngine.registerFlow, and an artifact's parse. No consumer code changes.QueryPageWindowSchemaisint().nonnegative(), and0means "the total alone" at a list door. Aget_recordnode always reads at least one row, so0there is not a window. The bound is its own, and the TSDoc says why.not-required (already-registered flow-builtin-node-config-values-refused), amended in this diff. That D3 entry already covers "a value outside the declared set or range" for builtin node configs. Its surface, replacement and reason now nameget_recordlimit. The reason also says the one difference: such a node used to run rather than fail. No D2 conversion, because only the author knows the count they meant..changeset/25302-get-record-limit-positive-integer.md,@objectstack/specminor, with FROM → TO and the one-line fix.content/docs/references/automation/builtin-node-config.mdx(check:generated --fix, 1 of 14 artifacts stale:check:docs). The JSON Schema (gitignored) now readstype: integer,exclusiveMinimum: 0.Producers measured
97967a9: 70get_recordnodes in*.ts. Everylimitwithin 14 lines of one is a positive whole number (200 ×4, 5 ×5, 50 ×2, 10 ×2). The one other value is the string'{page.size}'incanonicalize-stored-flow.test.ts, which is already refused as the wrong type and still throws/limit/. Query:git grep -n -A14 -E "type: ?['\"]get_record['\"]"piped to a grep forlimit:.4166f21(currentmain): 42get_recordnodes. 12limits, all positive whole numbers (500 ×7, 1000 ×2, 1 ×1, andBILLING_HANDOFF_LINE_ITEM_LIMIT= 200 ×2).Tests
At head
d972d18(the spec diff is unchanged sincee92f351; the merge brought plugin-security/plugin-sharing only):flow-builtin-node-config-values.test.ts: probeslimit2.5 / 0 / -1 at the judge, and a newget_record limit is a positive integerblock. That block covers a fraction atFlowSchemain the contract's words;defineStackassertingcode+status(STACK_SCHEMA_INVALID/ 422) and the path, for 2.5, 0, -1, 0.5 and an unsafe integer; and the save-door schema. Controls:limit1, 2 and 200 parse, and nolimitparses.@objectstack/spectest: 709/709 files, 21566 passed. typecheck: exit 0.@objectstack/service-automation198/198 files, 2618 tests;@objectstack/lint153/153, 7076;@objectstack/metadata-protocol259 passed + 3 skipped, 21416 passed. typecheck exit 0 for all three. No fixture needed triage, because no fixture carries a non-positive or fractionallimit.scripts/ablation-replace.mjs, mutationint().positive()removed): the test file went 10 failed / 59 passed, which is exactly the 10 refusal cases. The controls stayed green. Restored: blob equals HEADa900d3e7andgit diff HEADis empty. Spec's own tests importsrc, so no build leg applies.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 113 families for this diff. Each was run locally with its exit code captured before any pipe. 112 exited 0.pnpm check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET: it reads the dist of about 40 packages this diff does not build), so it is NOT MEASURED locally and left to CI'sLint & Repo Gates.check:skill-examplesfirst exited 3 (no client dist), then exited 0 after building@objectstack/clientandclient-react. Three gates first exited 3 on a shallow clone or a missing lint dist, then exited 0 aftergit fetch --unshallowand building the lint closure.Acceptance notes
crud-nodes.tsdeclareslimit: { type: 'integer', title: 'Limit' }with nominimum. So a designer form built from it offers0, which the save door now refuses with the contract's words. That is outside this card's file surface. Noted, not filed: declared ≠ enforced does not apply, because the refusal is loud at save.if (limit && limit > 1)in the executor is now exact:limitis either absent or a whole number of at least 1.Generated by Claude Code