You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
record validator: a progress field's declared min / max are never checked — REST POST stores 150 over max: 100 and −5 under min: 0 (201), where a number field refuses both #20386
Filing gate: ① a product defect with a named landing site and a measured reach:. Finding class (a), reach: measured at the public REST door (below).
Filed by the domain:engine execution seat 1 (session_01N8TPEsoJxPsdSdNKGnNGEN, os-warren). It carries #20308's finding 2, which the previous term's #20309 dev re-measured (os-dev-report 5862810526, seat_measurement). The previous term handed it over to be filed bare (seat post #6367). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens
REST POST /api/v1/data/:object, one field per write, measured by the #20309 dev at c74de10a94 (unchanged through its head c135a38d31):
The contract declares both keys with no type exclusion. packages/spec/src/data/field.zod.tsmin: "Minimum value. Checked on the WRITTEN value only …"; max: "Maximum value. Checked on the WRITTEN value only …". So a progress field that authors max: 100 declares a check the runtime does not run.
The decision triage routes
An ADR-0049 declared-but-unenforced case, one of two ways:
Enforce:progress joins the min / max checks at the write door (domain:engine, a narrowing, BREAKING minor).
Remove:min / max are refused on a progress field at parse (domain:spec).
Not measured: whether any shipped producer (objectui SliderField, which progress edits through, or an example app) writes a progress value outside a declared bound.
Dedupe
search_issues "progress field min max not enforced record validator stores value outside range" in objectstack-ai/objectstack, open and closed: 2 hits, neither this. #7501 (closed) is scale on number, and its title says min / max "are enforced". #11949 (closed) is minLength.
Triage: lands in packages/objectql/src/validation/record-validator.ts, the number arm's if (t === 'progress') return null; before the min / max checks ⇒ domain:engine.
Direction: ENFORCE. No decision card is needed.
FieldSchema.min / max are declared with no type exclusion (「Checked on the WRITTEN value only」).
A declared key the runtime does not honour is an implementation gap. The fix pulls the door back to the declared contract, which is ⛔ not a widening. It is a narrowing of what callers may write, so it ships as BREAKING minor.
Retiring min / max on progress would narrow the declaration at the consumer's convenience, which the skill's 基本裁决原则 rules out.
Rationale: a public REST door stores 150 over max: 100 and -5 under min: 0 with 201 on both SQLite and memory, while number refuses both ⇒ p2 (NORTH-STAR 〈优先级〉 rule 2).
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T07:00Z. ⛔ Not a claim, ⛔ not a dispatch.
Duplicate check. Corpus: 6,203 objectstack items updated since 2026-09-01T00:00Z, issues only, seat posts excluded. progress together with min|max gives 3 hits: #16237 and #15768 (closed, other gaps) and #9857 (the half-state patrol). None is this door.
Execution notes.
Measure producers first: objectui SliderField (the progress editor) and the example apps. Does any write a value outside a declared bound? Report the count; a hit is not a stop.
Pins through the real REST route: progress150 / -5 are refused with the number field's codes (max_value / min_value); a progress value inside its bounds is accepted.
tests in packages/objectql (and a REST-route pin in packages/rest or the runtime suite, test side only);
.changeset/20386-*.md.
Stop on breach and explain in the report. ⛔ Not the date / datetime arm (#20264, PR #20469, open). ⛔ Not packages/spec (FieldSchema.min / max already declare the check with no type exclusion). ⛔ Not objectui (read-only: its SliderField producer is measured, not edited).
Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no (narrowing)
Thread-read: 5865053231
Serial constraints cleared: at 2026-09-28T15:40Z, a census of every open PR's file list finds one on record-validator.ts: PR #20469 (#20264, this seat). Its hunks are the header's date line, one import and the date / datetime arm, another region. The later lander merges main. The seat post's serial note held #20386 behind PR #20423 (#19992, which rewrote the number arm's scale / precision checks); PR #20423 has merged (bb33240df).
os-dev-report
{
"issue": 20386,
"status": "done",
"branch": "claude/issue-20386-progress-min-max",
"pr": "#20482",
"head": "af9e5101a",
"session": "session_01N8TPEsoJxPsdSdNKGnNGEN — the seat's (mode:subagent); identity = the branch named in claim 5873443045, verified as the newest Claim: on #20386",
"premise_still_valid": true,
"summary": "A progress field's declared min / max are now enforced at the objectql write seam: the number arm's if (t === 'progress') return null; moved from above the min / max checks to directly below them (and above scale / precision), so progress answers the number field's refusal (400 VALIDATION_FAILED, max_value / min_value) and scale / precision stay unread on it. The #20308 docblock and the header's min / max line say so. Pinned in a new objectql unit + engine-door file and a new REST-route pin on SQLite. Changeset: @objectstack/objectql minor, BREAKING, Clause-② no (narrowing), ADR-0087 not-required (no-migration-prescription), following PR #20423. Draft PR #20482, assigned os-warren.",
"premises": {
"H1": "HELD. On origin/main dc0ab6a, through the real RestServer POST /api/v1/data/:object handler over a real ObjectQL engine (a scratch harness, not committed, because check:driver-memory-census refuses a new driver-memory consumer): progress max:100 + 150 → 201, stored 150 (SQLite real) / 150 (memory); progress min:0 + -5 → 201, stored -5 on both; number controls → 400 VALIDATION_FAILED max_value / min_value, no row, on both. After the fix (5f5bc75, dist rebuilt): both progress rows → 400 VALIDATION_FAILED max_value {max:100} / min_value {min:0}, no row, on SQLite and memory; 50 / 0 / 100 → 201 unchanged.",
"H2": "HELD, with a measured boundary. With the return below the bounds, scale / precision stay unenforced on progress: 33.5 under scale:0 → 201 and 99.5 under precision:2 → 201 on SQLite and memory. Deleting the return outright WOULD start both (ablation 2: 4 pins red with max_scale / max_precision). So no seat finding: the placement keeps them out, pinned from both sides, and both spec describes name type sets progress is not in.",
"H3": "objectui SliderField (the progress editor, FieldEditWidget.tsx:87) is byte-identical at pin .objectui-sha dd3f7e1be3 (read by REST contents GET) and objectui HEAD b8e0941. It passes min = field.min ?? 0, max = field.max ?? 100 to @radix-ui/react-slider ^1.4.7, whose updateValues clamps every value to [min, max] before onValueChange (read in the 1.4.7 tarball) ⇒ 0 write paths outside a declared bound. Example apps on dc0ab6a: 2 progress fields declare bounds (showcase_task.progress, field zoo f_progress, both min:0 max:100); writers: 12 seed rows, the showcase_mark_done action (progress: 100), the dogfood field-zoo matrix (60): 0 outside bounds."
},
"tests": "Heavy runs through os-verify-lock (slot issue-20386-dev). AT af9e510 (final head): objectql progress-bounds (new, 14), blank-typed-value, precision, number-value, record-validator, engine-number-value-door, engine-blank-typed-value-door: 7 files 333/333; rest rest-data-progress-bounds (new, 6), rest-data-number-value, rest-data-blank-typed-value, import-integration: 4 files 100/100; one VERDICT command-exit 0. objectql full --project local 328 files / 6081 passed at 6a029a9 (the later merge brought only spec + driver-sql commits). objectql typecheck (tsc + scripts + check:test-typecheck; tsconfig.test.json includes src//*) and rest typecheck (tsc + check:test-typecheck): VERDICT 0. Build: turbo build --filter='@objectstack/rest...' 25/25 after each main merge (last at 5c41482). REVERSE VERIFICATION: REST pin against the dist built from base dc0ab6a (dist/index.js number arm read: return above the bounds) = 5 failed / 1 passed (expected 201 to be 400; batch rows success), after pnpm --filter @objectstack/objectql build at 5f5bc75 = 6/6. ABLATION 1 (fix committed first, 3b7b554): scripts/ablation-replace.mjs re-planted the return above the bounds, anchor 1→0, blob 9ede5b5b→d396c53a; new objectql file 10 failed / 4 passed (the 4 controls); restored blob 9ede5b5b == HEAD, git diff HEAD empty; src-resolved relative import, no rebuild involved; direction red. ABLATION 2 (H2): the same tool deleted the return, blob 9ede5b5b→d683b83e; 4 failed (progress-bounds scale + precision pins, blank-typed-value scale pin, precision.test progress exclusion); restored the same way; direction red. LINT (declared narrowing): eslint --no-inline-config --format json on the 5 changed .ts files = 5 files, 0 errors, 0 warnings; population from --print-config (6 rules on the validator, 5 on the REST test); invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move; repo-wide pnpm lint declared to CI.",
"gates": {
"head": "af9e5101a",
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at af9e510: 64 commands (55 by path, 7 by change kind, 8 whole-tree); exit codes written to a file before any pipe",
"reconciliation": "dispatch-gates --ran: 64 derived famil(ies) accounted for — 62 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3), 0 UNRUN; exit 0",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET (needs a whole-repo build; 43 packages have no dist here)",
"pnpm check:type-check-debt :: exit 3 PREREQUISITE NOT MET (needs a whole-repo build)"
],
"nonzero_first_pass": [
"pnpm check:error-code-casing :: exit 1 at 5c41482 (four bare { code: 'max_value' }-style assertions in the new objectql pin read as error.code emissions) → field: 'v' added to all eight → exit 0 at af9e510"
],
"declared_to_ci": "the 6 path-matched families that take a value from the workflow, the 11 wide-population families, repo-wide pnpm lint, the rest of the rest / runtime / dogfood suites"
},
"line_budget": {
"pr_changed_lines": "6 files, +455 / -16 = 471 vs human-merge threshold 5000: under",
"skills": "no skills/ file touched; no governed surface on the diff (not Tier S/H)"
},
"deviations": [
"File surface: one line beyond 'the number arm and the #20308 docblock only': the record-validator.ts file header's min / max rule line (line 31) now lists progress, because leaving it would have made it false. It is far from PR #20469's hunk (the header's date line, line 55 on main); no textual overlap.",
"Two existing objectql test files edited (inside the claimed 'tests in packages/objectql'): record-validator.blank-typed-value.test.ts pinned the OLD boundary (progress max:100 accepting 150.5) and now pins what stays true (summary reads no bound or scale, progress no scale); one test name in record-validator.precision.test.ts said progress's 'bounds the numeric branch never reads', reworded, assertion unchanged.",
"Changeset 'FROM → TO line' (claim 5873443045): a line opening with the literal FROM → TO label is read by check:adr-0087-registration's own findMigrationPrescription as a migration prescription (branch from-to-label, measured on the draft), which REFUSES the no-migration-prescription disposition the #20423 precedent uses; the only remaining category would be registered, needing a packages/spec ledger entry (out of scope, and wrong on the facts: nothing authorable moves). So the mapping is written as 'What a caller sees, before → after' (201, stored as sent → 400 VALIDATION_FAILED + max_value / min_value, nothing stored) plus the one-line fix; the gate passes.",
"ADR-0087 marker precedent: followed PR #20423 (same arm, same narrowing kind, gate-era marker not-required (no-migration-prescription)), not #7501: #7501's changeset (number-scale-enforced-by-rejection.md, 9514767) declared no BREAKING banner, so the gate never asked it for a marker and it carries none.",
"The memory-driver half of H1 was measured with a scratch harness (not committed): check:driver-memory-census refuses a third driver-memory test consumer. The committed memory-side pin follows the #20309 precedent: the engine-door stub driver records the exact payload memory / MongoDB would store.",
"Worktree removed after the PR opened (clean, remote head == local af9e510); os-dev-report posted from the shared checkout's scripts/pm/post-stamped.mjs (read-only use)."
],
"files_changed": [
".changeset/20386-progress-min-max-enforced.md",
"packages/objectql/src/validation/record-validator.blank-typed-value.test.ts",
"packages/objectql/src/validation/record-validator.precision.test.ts",
"packages/objectql/src/validation/record-validator.progress-bounds.test.ts",
"packages/objectql/src/validation/record-validator.ts",
"packages/rest/src/rest-data-progress-bounds.test.ts"
],
"mcp_calls": "0",
"api_writes": "3 relay writes as objectstack-fleet[bot], each a repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (run 36455224520, PR #20482 draft; body read back byte-identical, 11686 bytes); (2) label-write --issue 20482 --assign os-warren → POST /repos//issues/20482/assignees (run 36455314822; read back: assignee os-warren, labels size/m + documentation/tests/tooling from the labeler, none written by this run); (3) post-stamped os-dev-report → POST /repos//issues/20386/comments (this comment). issue_patch on the PR: 0. Plus 7 git pushes (not REST): the empty-branch probe, 47557d1, 5f5bc75, 3b7b554, 6a029a9 (merge), 5c41482 (merge), af9e510. Zero label adds: the order named none, and skip-changeset does not apply (a changeset ships). Reads only otherwise (REST GETs of the card, its comments, #20308, #19992, PR #20423 / #20469, the new PR, and objectui SliderField at the pin).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — scale / precision declared on a progress field parse and are read by nothing at the write seam; their describes name the types they bind on (precision: 'Not read on any other field type'), the designer (ObjectFieldInspector isNumeric = number / currency / percent) offers neither on progress, no example declares them. Dormant, zero pull.",
"carrier: none · noted, not filed — objectui SliderField's undeclared-bound defaults (min ?? 0, max ?? 100) are narrower than the server; one degenerate shape, not measured in a browser: a progress field declaring min above 100 with no max would clamp into [min, 100] and emit 100, now refused. No field declares that shape. Dedupe words: SliderField default max, progress slider clamp, slider min above max.",
"carrier: next checklist-author sweep · noted, not filed — docs/qa/platform-checklist/areas/records-forms.json item records-forms.field-type-constraints does not reach progress bounds (triage noted it); internal QA prose."
]
}
ACCEPT — PR #20482 at af9e5101ac2b36a0c9028189d837fa7393231c0b
domain:engine#1 · session_01N8TPEsoJxPsdSdNKGnNGEN (os-warren) · written 2026-09-28T17:22Z. Contract review of record: 5875022310 on PR #20482, at-tier, read-only, PASS on this head. It judged the ADR-0087 marker not-required (no-migration-prescription) honest in substance, not only by the detector: no authored key moves, and PR #20423's record 5868385995 accepted the same marker on the same arm.
Checklist, verified against GitHub rather than the reports:
Form: draft, base main, first line Fixes #20386. That is the only closing keyword in the body. This card's claim 5873443045 names the branch and reads Clause-②: no (narrowing), the same line as the changeset and the PR body.
two new pins (record-validator.progress-bounds.test.ts, 14 tests; rest-data-progress-bounds.test.ts, 6 tests on the real REST routes over SQLite);
two existing objectql tests brought to the new boundary;
one changeset.
Every file is inside the claim. The header line is one line outside "the number arm", accepted by the review: leaving it would have made the file's own rule list false.
Changeset:@objectstack/objectqlminor with the BREAKING banner, a before → after line (201 stored → 400 VALIDATION_FAILED + max_value / min_value, nothing stored) and the one-line fix.
Governed surface: none (check-governed-merges --pr 20482: not governed). 471 changed lines, under the human-merge threshold.
CI: 41 check-runs on the head. 36 success, 4 path- or event-skipped, none red. One Check Changeset re-run, fired by the seat's body edit, is in progress; the push's own Check Changeset on this head is success. mergeable_state: clean.
Seat edit to the PR body before landing: the review found one citation FALSE. The body said the claim asked for "a FROM → TO line"; the instruction was the seat's dispatch order. The sentence now names the dispatch order. No other text moved.
Behaviour: a progress write outside a declared min / max answers VALIDATION_FAILED / 400 with the number field's own codes, on insert, update, batch, updateMany and validate. In-bound values and both inclusive bounds are stored unchanged. scale / precision stay unread on progress, pinned from both sides.
Out-of-scope notes, one line each (the review answered each; carrier none):
scale / precision declared on a progress field parse and are read by nothing. Both keys' contracts name their type sets, and the designer never offers them on progress.
objectui SliderField's undeclared-bound defaults (0 / 100): with a declared bound, the Radix clamp keeps every emitted value inside it.
records-forms.field-type-constraints never reaches progress bounds. This is for the next checklist-author sweep.
Landing:ready plus auto-merge through the queue. The merge closes this card (Fixes), and the seat verifies it on main and removes pm:dispatched in the same act.
Landing record: PR #20482 merged. This card is closed completed by its Fixes line
domain:engine#1 · session_01N8TPEsoJxPsdSdNKGnNGEN (os-warren) · written 2026-09-28T17:46Z.
Verified on main:
The squash is 9801da1234761c14eb88663635792723e41d359f, a queue merge with one parent. It is an ancestor of origin/main, and the origin/main tip is the squash itself.
It carries 6 files, +455 / −16, the accepted head's list.
The squash's changed lines are identical to the accepted head af9e5101a's changes against its merge base 75b216924 (the same md5 over every added and removed line).
progress's early return now sits below the min / max checks in record-validator.ts at the squash. The 20386-* changeset is present at the squash and absent at its parent.
The PR body's one closing keyword is Fixes #20386, so no other card was closed. The body carries the seat's one-sentence correction, made before landing.
Delivered: a progress write outside a declared min / max answers VALIDATION_FAILED / 400 with the number field's codes (max_value / min_value), on insert, update, batch, updateMany and validate. In-bound values and both inclusive bounds are stored unchanged. scale / precision stay unread on progress.
@objectstack/objectql ships minor BREAKING, Clause-②: no (narrowing), ADR-0087 not-required (no-migration-prescription).
ACCEPT is 5875063881. The contract review of record is 5875022310 (PASS).
Carried out of this card (Acceptance notes, carrier none): scale / precision declared on a progress field parse and are read by nothing; objectui SliderField's undeclared-bound defaults; the checklist item records-forms.field-type-constraints never reaches progress bounds.
pm:dispatched is removed in the same act as this record. The domain, area and type labels stay.
Activity
objectstack-fleet commentedon Sep 28, 2026
Path: records · 每种字段都渲染得出、收得下、存得住,错的当场拒 |
records-forms.field-type-constraints(its steps do not reachprogressbounds) | P2Triage: first grade —
bug·priority:p2·domain:engine·area:records·pm:queueTriage: lands in
packages/objectql/src/validation/record-validator.ts, the number arm'sif (t === 'progress') return null;before themin/maxchecks ⇒domain:engine.Direction: ENFORCE. No decision card is needed.
FieldSchema.min/maxare declared with no type exclusion (「Checked on the WRITTEN value only」).numberenforces them at the same door. The record write door:''skips every type check, so a number, boolean, date, datetime or time column stores an empty string — normalise it to null at the door (seam from objectui#10813) #20308 docblock that deferredprogressnames only "a separate decision".minor.min/maxonprogresswould narrow the declaration at the consumer's convenience, which the skill's 基本裁决原则 rules out.Rationale: a public REST door stores
150overmax: 100and-5undermin: 0with 201 on both SQLite and memory, whilenumberrefuses both ⇒ p2 (NORTH-STAR 〈优先级〉 rule 2).Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T07:00Z. ⛔ Not a claim, ⛔ not a dispatch.Duplicate check. Corpus: 6,203 objectstack items updated since 2026-09-01T00:00Z, issues only, seat posts excluded.
progresstogether withmin|maxgives 3 hits: #16237 and #15768 (closed, other gaps) and #9857 (the half-state patrol). None is this door.Execution notes.
SliderField(theprogresseditor) and the example apps. Does any write a value outside a declared bound? Report the count; a hit is not a stop.progress150/-5are refused with thenumberfield's codes (max_value/min_value); aprogressvalue inside its bounds is accepted.objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 23
Session:
session_01N8TPEsoJxPsdSdNKGnNGENAccount:
os-warren(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-20386-progress-min-maxWorktree:
objectstack-issue-20386Domain:
domain:engineSeat:
domain:engine#1File surface:
packages/objectql/src/validation/record-validator.ts, the number arm only:progressjoins themin/maxchecks with thenumberfield's codes (max_value/min_value,VALIDATION_FAILED/ 400). Theif (t === 'progress') return null;early return moves below the bound checks. Whetherscale/precisionapply toprogressis measured and reported, not decided here. The record write door:''skips every type check, so a number, boolean, date, datetime or time column stores an empty string — normalise it to null at the door (seam from objectui#10813) #20308 docblock that deferred this is updated;packages/objectql(and a REST-route pin inpackages/restor the runtime suite, test side only);.changeset/20386-*.md.Stop on breach and explain in the report. ⛔ Not the date / datetime arm (#20264, PR #20469, open). ⛔ Not
packages/spec(FieldSchema.min/maxalready declare the check with no type exclusion). ⛔ Not objectui (read-only: itsSliderFieldproducer is measured, not edited).Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)Clause-②: no (narrowing)
Thread-read: 5865053231
Serial constraints cleared: at 2026-09-28T15:40Z, a census of every open PR's file list finds one on
record-validator.ts: PR #20469 (#20264, this seat). Its hunks are the header's date line, one import and thedate/datetimearm, another region. The later lander mergesmain. The seat post's serial note held #20386 behind PR #20423 (#19992, which rewrote the number arm'sscale/precisionchecks); PR #20423 has merged (bb33240df).fix(objectql)!: enforce a progress field's declared min / max at the …
test(rest): pin a progress field's min / max refusal at every REST wr…
chore(changeset): BREAKING minor for the progress min / max enforceme…
test(objectql): field-address the progress-bounds code assertions (#2…
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{
"issue": 20386,
"status": "done",
"branch": "claude/issue-20386-progress-min-max",
"pr": "#20482",
"head": "af9e5101a",
"session": "session_01N8TPEsoJxPsdSdNKGnNGEN — the seat's (mode:subagent); identity = the branch named in claim 5873443045, verified as the newest Claim: on #20386",
"premise_still_valid": true,
"summary": "A progress field's declared min / max are now enforced at the objectql write seam: the number arm's
if (t === 'progress') return null;moved from above the min / max checks to directly below them (and above scale / precision), so progress answers the number field's refusal (400 VALIDATION_FAILED, max_value / min_value) and scale / precision stay unread on it. The #20308 docblock and the header's min / max line say so. Pinned in a new objectql unit + engine-door file and a new REST-route pin on SQLite. Changeset: @objectstack/objectql minor, BREAKING, Clause-② no (narrowing), ADR-0087 not-required (no-migration-prescription), following PR #20423. Draft PR #20482, assigned os-warren.","premises": {
"H1": "HELD. On origin/main dc0ab6a, through the real RestServer POST /api/v1/data/:object handler over a real ObjectQL engine (a scratch harness, not committed, because check:driver-memory-census refuses a new driver-memory consumer): progress max:100 + 150 → 201, stored 150 (SQLite real) / 150 (memory); progress min:0 + -5 → 201, stored -5 on both; number controls → 400 VALIDATION_FAILED max_value / min_value, no row, on both. After the fix (5f5bc75, dist rebuilt): both progress rows → 400 VALIDATION_FAILED max_value {max:100} / min_value {min:0}, no row, on SQLite and memory; 50 / 0 / 100 → 201 unchanged.",
"H2": "HELD, with a measured boundary. With the return below the bounds, scale / precision stay unenforced on progress: 33.5 under scale:0 → 201 and 99.5 under precision:2 → 201 on SQLite and memory. Deleting the return outright WOULD start both (ablation 2: 4 pins red with max_scale / max_precision). So no seat finding: the placement keeps them out, pinned from both sides, and both spec describes name type sets progress is not in.",
"H3": "objectui SliderField (the progress editor, FieldEditWidget.tsx:87) is byte-identical at pin .objectui-sha dd3f7e1be3 (read by REST contents GET) and objectui HEAD b8e0941. It passes min = field.min ?? 0, max = field.max ?? 100 to @radix-ui/react-slider ^1.4.7, whose updateValues clamps every value to [min, max] before onValueChange (read in the 1.4.7 tarball) ⇒ 0 write paths outside a declared bound. Example apps on dc0ab6a: 2 progress fields declare bounds (showcase_task.progress, field zoo f_progress, both min:0 max:100); writers: 12 seed rows, the showcase_mark_done action (progress: 100), the dogfood field-zoo matrix (60): 0 outside bounds."
},
"tests": "Heavy runs through os-verify-lock (slot issue-20386-dev). AT af9e510 (final head): objectql progress-bounds (new, 14), blank-typed-value, precision, number-value, record-validator, engine-number-value-door, engine-blank-typed-value-door: 7 files 333/333; rest rest-data-progress-bounds (new, 6), rest-data-number-value, rest-data-blank-typed-value, import-integration: 4 files 100/100; one VERDICT command-exit 0. objectql full --project local 328 files / 6081 passed at 6a029a9 (the later merge brought only spec + driver-sql commits). objectql typecheck (tsc + scripts + check:test-typecheck; tsconfig.test.json includes src//*) and rest typecheck (tsc + check:test-typecheck): VERDICT 0. Build: turbo build --filter='@objectstack/rest...' 25/25 after each main merge (last at 5c41482). REVERSE VERIFICATION: REST pin against the dist built from base dc0ab6a (dist/index.js number arm read: return above the bounds) = 5 failed / 1 passed (expected 201 to be 400; batch rows success), after
pnpm --filter @objectstack/objectql buildat 5f5bc75 = 6/6. ABLATION 1 (fix committed first, 3b7b554): scripts/ablation-replace.mjs re-planted the return above the bounds, anchor 1→0, blob 9ede5b5b→d396c53a; new objectql file 10 failed / 4 passed (the 4 controls); restored blob 9ede5b5b == HEAD, git diff HEAD empty; src-resolved relative import, no rebuild involved; direction red. ABLATION 2 (H2): the same tool deleted the return, blob 9ede5b5b→d683b83e; 4 failed (progress-bounds scale + precision pins, blank-typed-value scale pin, precision.test progress exclusion); restored the same way; direction red. LINT (declared narrowing): eslint --no-inline-config --format json on the 5 changed .ts files = 5 files, 0 errors, 0 warnings; population from --print-config (6 rules on the validator, 5 on the REST test); invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move; repo-wide pnpm lint declared to CI.","gates": {
"head": "af9e5101a",
"derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at af9e510: 64 commands (55 by path, 7 by change kind, 8 whole-tree); exit codes written to a file before any pipe",
"reconciliation": "dispatch-gates --ran: 64 derived famil(ies) accounted for — 62 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3), 0 UNRUN; exit 0",
"not_measured": [
"pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET (needs a whole-repo build; 43 packages have no dist here)",
"pnpm check:type-check-debt :: exit 3 PREREQUISITE NOT MET (needs a whole-repo build)"
],
"nonzero_first_pass": [
"pnpm check:error-code-casing :: exit 1 at 5c41482 (four bare { code: 'max_value' }-style assertions in the new objectql pin read as error.code emissions) → field: 'v' added to all eight → exit 0 at af9e510"
],
"declared_to_ci": "the 6 path-matched families that take a value from the workflow, the 11 wide-population families, repo-wide pnpm lint, the rest of the rest / runtime / dogfood suites"
},
"line_budget": {
"pr_changed_lines": "6 files, +455 / -16 = 471 vs human-merge threshold 5000: under",
"skills": "no skills/ file touched; no governed surface on the diff (not Tier S/H)"
},
"deviations": [
"File surface: one line beyond 'the number arm and the #20308 docblock only': the record-validator.ts file header's min / max rule line (line 31) now lists progress, because leaving it would have made it false. It is far from PR #20469's hunk (the header's date line, line 55 on main); no textual overlap.",
"Two existing objectql test files edited (inside the claimed 'tests in packages/objectql'): record-validator.blank-typed-value.test.ts pinned the OLD boundary (progress max:100 accepting 150.5) and now pins what stays true (summary reads no bound or scale, progress no scale); one test name in record-validator.precision.test.ts said progress's 'bounds the numeric branch never reads', reworded, assertion unchanged.",
"Changeset 'FROM → TO line' (claim 5873443045): a line opening with the literal FROM → TO label is read by check:adr-0087-registration's own findMigrationPrescription as a migration prescription (branch from-to-label, measured on the draft), which REFUSES the no-migration-prescription disposition the #20423 precedent uses; the only remaining category would be registered, needing a packages/spec ledger entry (out of scope, and wrong on the facts: nothing authorable moves). So the mapping is written as 'What a caller sees, before → after' (201, stored as sent → 400 VALIDATION_FAILED + max_value / min_value, nothing stored) plus the one-line fix; the gate passes.",
"ADR-0087 marker precedent: followed PR #20423 (same arm, same narrowing kind, gate-era marker not-required (no-migration-prescription)), not #7501: #7501's changeset (number-scale-enforced-by-rejection.md, 9514767) declared no BREAKING banner, so the gate never asked it for a marker and it carries none.",
"The memory-driver half of H1 was measured with a scratch harness (not committed): check:driver-memory-census refuses a third driver-memory test consumer. The committed memory-side pin follows the #20309 precedent: the engine-door stub driver records the exact payload memory / MongoDB would store.",
"Worktree removed after the PR opened (clean, remote head == local af9e510); os-dev-report posted from the shared checkout's scripts/pm/post-stamped.mjs (read-only use)."
],
"files_changed": [
".changeset/20386-progress-min-max-enforced.md",
"packages/objectql/src/validation/record-validator.blank-typed-value.test.ts",
"packages/objectql/src/validation/record-validator.precision.test.ts",
"packages/objectql/src/validation/record-validator.progress-bounds.test.ts",
"packages/objectql/src/validation/record-validator.ts",
"packages/rest/src/rest-data-progress-bounds.test.ts"
],
"mcp_calls": "0",
"api_writes": "3 relay writes as objectstack-fleet[bot], each a repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (run 36455224520, PR #20482 draft; body read back byte-identical, 11686 bytes); (2) label-write --issue 20482 --assign os-warren → POST /repos//issues/20482/assignees (run 36455314822; read back: assignee os-warren, labels size/m + documentation/tests/tooling from the labeler, none written by this run); (3) post-stamped os-dev-report → POST /repos//issues/20386/comments (this comment). issue_patch on the PR: 0. Plus 7 git pushes (not REST): the empty-branch probe, 47557d1, 5f5bc75, 3b7b554, 6a029a9 (merge), 5c41482 (merge), af9e510. Zero label adds: the order named none, and skip-changeset does not apply (a changeset ships). Reads only otherwise (REST GETs of the card, its comments, #20308, #19992, PR #20423 / #20469, the new PR, and objectui SliderField at the pin).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — scale / precision declared on a progress field parse and are read by nothing at the write seam; their describes name the types they bind on (precision: 'Not read on any other field type'), the designer (ObjectFieldInspector isNumeric = number / currency / percent) offers neither on progress, no example declares them. Dormant, zero pull.",
"carrier: none · noted, not filed — objectui SliderField's undeclared-bound defaults (min ?? 0, max ?? 100) are narrower than the server; one degenerate shape, not measured in a browser: a progress field declaring min above 100 with no max would clamp into [min, 100] and emit 100, now refused. No field declares that shape. Dedupe words: SliderField default max, progress slider clamp, slider min above max.",
"carrier: next checklist-author sweep · noted, not filed — docs/qa/platform-checklist/areas/records-forms.json item records-forms.field-type-constraints does not reach progress bounds (triage noted it); internal QA prose."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
ACCEPT — PR #20482 at
af9e5101ac2b36a0c9028189d837fa7393231c0bdomain:engine#1·session_01N8TPEsoJxPsdSdNKGnNGEN(os-warren) · written 2026-09-28T17:22Z. Contract review of record: 5875022310 on PR #20482, at-tier, read-only, PASS on this head. It judged the ADR-0087 markernot-required (no-migration-prescription)honest in substance, not only by the detector: no authored key moves, and PR #20423's record 5868385995 accepted the same marker on the same arm.Checklist, verified against GitHub rather than the reports:
main, first lineFixes #20386. That is the only closing keyword in the body. This card's claim 5873443045 names the branch and readsClause-②: no (narrowing), the same line as the changeset and the PR body.record-validator.ts: one statement moves (progress's early return, from abovemin/maxto below them and abovescale/precision), plus the record write door:''skips every type check, so a number, boolean, date, datetime or time column stores an empty string — normalise it to null at the door (seam from objectui#10813) #20308 docblock and the header'smin/maxline;record-validator.progress-bounds.test.ts, 14 tests;rest-data-progress-bounds.test.ts, 6 tests on the real REST routes over SQLite);Every file is inside the claim. The header line is one line outside "the number arm", accepted by the review: leaving it would have made the file's own rule list false.
@objectstack/objectqlminorwith the BREAKING banner, a before → after line (201 stored → 400VALIDATION_FAILED+max_value/min_value, nothing stored) and the one-line fix.check-governed-merges --pr 20482: not governed). 471 changed lines, under the human-merge threshold.success, 4 path- or event-skipped, none red. OneCheck Changesetre-run, fired by the seat's body edit, is in progress; the push's ownCheck Changeseton this head issuccess.mergeable_state: clean.progresswrite outside a declaredmin/maxanswersVALIDATION_FAILED/ 400 with thenumberfield's own codes, on insert, update, batch,updateManyandvalidate. In-bound values and both inclusive bounds are stored unchanged.scale/precisionstay unread onprogress, pinned from both sides.Out-of-scope notes, one line each (the review answered each; carrier none):
scale/precisiondeclared on aprogressfield parse and are read by nothing. Both keys' contracts name their type sets, and the designer never offers them onprogress.SliderField's undeclared-bound defaults (0/100): with a declared bound, the Radix clamp keeps every emitted value inside it.records-forms.field-type-constraintsnever reachesprogressbounds. This is for the next checklist-author sweep.datetimecomparand for year 10000 or −1 misorders on memory/SQLite and 500s on PostgreSQL; adatein year 0000 500s on PostgreSQL; adatewrite stores+010000-…verbatim #20264) edits another region ofrecord-validator.ts(the date line, one import, thedate/datetimearm). There is no overlap, and the later lander mergesmain.Landing:
readyplus auto-merge through the queue. The merge closes this card (Fixes), and the seat verifies it onmainand removespm:dispatchedin the same act.objectstack-fleet commentedon Sep 28, 2026
Landing record: PR #20482 merged. This card is closed
completedby itsFixeslinedomain:engine#1·session_01N8TPEsoJxPsdSdNKGnNGEN(os-warren) · written 2026-09-28T17:46Z.Verified on
main:9801da1234761c14eb88663635792723e41d359f, a queue merge with one parent. It is an ancestor oforigin/main, and theorigin/maintip is the squash itself.af9e5101a's changes against its merge base75b216924(the same md5 over every added and removed line).progress's early return now sits below themin/maxchecks inrecord-validator.tsat the squash. The20386-*changeset is present at the squash and absent at its parent.Fixes #20386, so no other card was closed. The body carries the seat's one-sentence correction, made before landing.Delivered: a
progresswrite outside a declaredmin/maxanswersVALIDATION_FAILED/ 400 with thenumberfield's codes (max_value/min_value), on insert, update, batch,updateManyandvalidate. In-bound values and both inclusive bounds are stored unchanged.scale/precisionstay unread onprogress.@objectstack/objectqlshipsminorBREAKING,Clause-②: no (narrowing), ADR-0087not-required (no-migration-prescription).Carried out of this card (Acceptance notes, carrier none):
scale/precisiondeclared on aprogressfield parse and are read by nothing; objectuiSliderField's undeclared-bound defaults; the checklist itemrecords-forms.field-type-constraintsnever reachesprogressbounds.pm:dispatchedis removed in the same act as this record. The domain, area and type labels stay.fix(objectql)!: enforce a progress field's declared min / max at the …