Repository navigation
fix(cli)!: os validate and os build refuse a seed record value the seed load would refuse, in the load's words - #27600
objectstack-fleet[bot] wants to merge 5 commits into
Conversation
…s with the insert write's value verdict Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
… seed load refuses the same rows in the same words Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
…he seed walk's folded read is classified Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
…d; both packages minor Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 47 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 5b77e083217046689ec967460bbe6612b48de059 && git checkout 5b77e083217046689ec967460bbe6612b48de059
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 144702ca5dd1bb74bd6c0151068d75c73c00c3c5 a44c5261869245bd52c58fb4cdc408909d310eb3 && git checkout -B drift-repro 144702ca5dd1bb74bd6c0151068d75c73c00c3c5 && git merge --no-ff a44c5261869245bd52c58fb4cdc408909d310eb3
node scripts/docs-audit/affected-docs.mjs --json 144702ca5dd1bb74bd6c0151068d75c73c00c3c5
|
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head 8e7f125 · 2026-10-29T15:20Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Check-runs on the head: 53 total, 35 success, 18 skipped (opt-in and UI-only lanes), 0 failure. Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint, Type Check, Test Core 1-8, Dogfood Regression Gate all green. These are the gate verdicts this record relies on. ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on Tried to break it:
Findings: 0 must-fix; 2 should-fix (name the hook-rewrite limit and the Implemented-by: VERDICT: PASS Generated by Claude Code |
…rage limits (#27259) Contract review should-fixes, comment and changeset text only: the walk's header names that a beforeInsert hook is not run at authoring time and that a dataset whose object is declared only under manifest.objects is unjudged; validateInsertValues' docblock no longer over-states that `required` is left to the load (an explicit [] on a required multi-value field is refused here, as at load). Claude-Session: https://claude.ai/code/session_01WGufziV1z63WH7moz8f5TS Co-authored-by: Claude <noreply@anthropic.com>
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract-review-claim: session_01L6bhgV4fMGJHSdSdx3u9mK · head a44c526 · 2026-10-29T16:26Z Generated by Claude Code |
|
triage#1 · os-sales · session_01L6bhgV4fMGJHSdSdx3u9mK Contract reviewServed-tier: Delta record. The previous record on this PR is comment 6501312345 (PASS) on head Check-runs on the head ① Derived judgmentsThe accept-set and public-surface judgments 1-9 and 11 of record 6501312345 stand as judged there: the move changes no code, so ①.10 (the wording nit) — judged afresh, CLOSED. The ② Semver levelStands as judged in record 6501312345, re-read on the head: the changeset frontmatter is unchanged ( ③ Boundary flagsStands as judged in record 6501312345: ④ Design judgmentThe narrowing itself stands as judged in record 6501312345: the spec declares no accept set for seed VALUES at the authoring door (
Findings: 0 must-fix; 0 should-fix (both of record 6501312345's should-fixes land in the tree, accurately); 0 nits (①.10 closed). Open merge condition, not a finding: Test Core (1/8)-(8/8) and Type Check · workspace on Implemented-by: VERDICT: PASS Generated by Claude Code |
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Requested by Yibo Wang · project thread
Fixes #27259
Claim: 6496966334 (amended 6499269644)
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none edited. No skill teaches that
os validatepasses a seed value the load refuses, and none teaches a{ value, currency }seed. Governed hits that list whatos validatechecks stay true as non-exhaustive lists:skills/objectstack-platform/references/operations.md:13,skills/objectstack-data/SKILL.md:453,skills/objectstack-ui/SKILL.md:284. The seed teaching (skills/objectstack-data/references/seeds.md,skills/objectstack-formula/SKILL.md:270oncelseed values) stays true: an expression value is left to the load. Named, not edited.ADR-0087:
not-required (no-migration-prescription), in the changeset. No key of any schema moves; the refusal moves earlier, from the seed load to the authoring commands, in the load's own words, and every row it names was already dropped at load.Metadata-protocol: narrowed, per the claim amendment. A seed valueos validateandos buildnewly refuse is a value refused at the authoring door, so the kind isnarrowedand both packages the diff moves takeminor. The measurement beside it: what the runtime stores does not move. The record validator refused these values at seed load before this PR and refuses the same values after it; the refusal only moves earlier. The changeset carries the BREAKING banner.维护者速读
os validate和os build现在用种子加载时同一个记录校验器检查每条种子记录的字段值;加载会拒收的值(比如货币字段写成{ value: 10, currency: 'USD' })在校验时就报错,错误码和文案与加载时一字不差,并定位到data[0].records[4].budget。os validate/os build的接受集;运行时接受与拒收的集合不变(这些行加载时本来就被拒)。仓内四个示例应用 873 个种子值全部照常通过。回滚 = revert 本 PR。packages/cli/src/utils/seed-record-values.ts(哪些值交给判定、哪些留给加载)、packages/objectql/src/validation/record-validator.ts的validateInsertValues(插入路径的同一套归一化与校验调用)、packages/cli/test/seed-record-values-load-parity.test.ts(与真实种子加载逐行对拍)。pnpm --filter @objectstack/cli exec vitest run test/seed-record-values-load-parity.test.ts→ 1 个用例绿:校验时拒收的行号 = 真实加载拒收的行号(0、3、4、5、6),且加载日志里的句子以校验给出的句子结尾。What changes
Before, measured on
origin/mainad3a640e. A scratch copy ofexamples/app-todowithbudget: Field.currency({ label: 'Budget' })ontodo_taskand one seed rowbudget: { value: 10, currency: 'USD' }:After, same copy, head
d914d366and later.@objectstack/objectql:validateInsertValues(objectSchema, row, options?), new export. The insert write's verdict on the values a row supplies: the insert door's two rewrites (normalizeBlankTypedValues,normalizeNumericStringValues), the multi-value wrap (normalizeMultiValueFields, scope'include'), then the samevalidateRecordInScope(…, 'insert', 'include', …)callObjectQL.insert()makes. It judges only the declared fields the row supplies with a non-missing value, sorequired(which a default or a hook may still satisfy) and undeclared names are left to the load. ⛔ One judge: nothing in it re-states a value rule; it composes the functions the insert path already calls, in the insert path's order.@objectstack/cli:judgeSeedRecordValues(stack)(utils/seed-record-values.ts). Owns only the WALK. The field map is the runtime registry's: a driverlessObjectQL(the shapeauthoring-filter-judge.tsmeasured) holding the stack's objects, picklists and picklist extensions, folded across packages (authoringRuleUnionStack). Datasets are read where they sit (datasetsOf, now exported frombundle-seed-files.ts): top level,manifest.data, eachpackages[i].manifest.data. Each issue is{ location, object, field, code, message }, with the validator's code and sentence.seedFile(path)(the load stores the file and writes its id;collectSeedFilesjudges the path); a reference, file or structured-JSON field (the load resolves a reference's natural key to an id, and the ADR-0104 shape check refuses only on a deployment whose migration evidence says so); a field bound to a picklist the stack does not declare (judgePicklistReferencesreports it); a dataset whose object the stack does not declare or the registry refuses.os validatestep 3d-bis, right after the seed-file gate;os buildstep 4a-0, right before it. Both exit 1 with the issues;--jsoncarries them aserrors(validate) andissues(build), the shapes each command's seed-file exit already uses.test/validate-build-gate-parity.test.tsholds the call inSHARED_NON_REGISTRY_GATES.packages/cli:objectsOf/registrationCopy(authoring-filter-judge.ts) anddatasetsOf/DatasetLike(bundle-seed-files.ts), so the registry build and the dataset walk are each written once. No published CLI surface changes.Shipped-seed reading (the narrowing)
Every seed this repository ships, run through the new check on head
98d28ec0(os validatein each app, plus a read-only probe counting the values the walk hands the judge):examples/app-todoexamples/app-crmexamples/app-showcasesys_business_unit)examples/app-multi-packageAll four apps answer
✓ Validation passed, exit 0, as before. The showcase'sfield-zoodataset covers every field type and passes. No platform object, plugin or service inpackages/**ships seed datasets (git grepfordefineSeed(andrecords: [outside tests: none). Dogfood boots these examples and is green on603ecf33. HotCRM fixtures are not in this repository and were not read.Tests
packages/objectql/src/validation/record-validator.test.ts, a new[#27259]block, 7 cases: a currency object is refused with exactly the fieldsvalidateRecord(…, 'insert')gives (invalid_number, the lesson, the label); a bare number passes; a numeric string, a blank and a scalar on a multi-value field pass; an omitted or null required value is not judged (CONTROL:validateRecordon insert refuses the omission); an undeclared name gets no answer (CONTROL); a readonly value's shape is judged, as on the insert path; the row is never mutated.packages/cli/src/utils/seed-record-values.test.ts, 11 cases: the currency object refused atdata[0].records[1].budgetwith the lesson; a bare number passes; rewritten values pass; an unreadable number, an unknown option, an unreadable date, a value belowminand a bad multi-value element are refused with their codes; a picklist-bound field is judged against the stack's picklist; an expression, a seed file, a reference, a file and a field on a picklist declared elsewhere are left to the load;requiredis not judged; CONTROL: an undeclared field and a dataset for an object outside the stack get no answer; a package body's dataset is located atpackages[1].manifest.data[0].records[0].budget; the stack is not mutated.packages/cli/test/seed-record-values-load-parity.test.ts(integration tier by itsnew ObjectQL(signal), 1 case: seven rows through the check and through a realSeedLoaderServiceload on anObjectQLengine over SQLite:memory:. The rows the check refuses are exactly the rows the load refuses (0, 3, 4, 5, 6), each load message ends with the check's sentence, and the two rows it passes are written.packages/cli/test/validate-build-gate-parity.test.ts:judgeSeedRecordValuesjoinsSHARED_NON_REGISTRY_GATES.packages/cli/test/normalized-call-sites.test.ts: aresolvedrow for the walk's folded read.Acceptance notes
defineSeeddataset,defineSeeditself already refuses an undeclared field when the config loads (✗ defineSeed('todo_task'): unknown field(s) in records — bogus_field., exit 1). That answer is byte-identical before and after (diffed, timing lines aside). On a plain dataset the new check gives no answer for the name.sys_business_unitin the showcase) is not judged: the stack does not declare the object, soos validatehas no field map for it. The runtime judges it at load as before.requiredcheck on a missing value, validation rules, reference resolution and permissions stay the load's. A supplied empty array on a required multi-value field is not missing: the check refuses itrequired, as the load's insert path does.beforeInserthook runs on a seed write before the record validator, and the authoring check runs no hooks. A stack whose hook rewrites a seed value into an accepted shape loads clean but is refused byos validate. The walk's header names this.manifest.objects, outsidepackages[], has no field map in the folded stack and is not judged (no false refusal). The walk's header names this.Verification
Package suites
pnpm --filter @objectstack/objectql teston98d28ec0(after it, objectql source moved only in a docblock,a44c5261) exit 0: Test Files 485 passed (485), Tests 9124 passed (9124).pnpm --filter @objectstack/objectql typecheckexit 0.pnpm --filter @objectstack/cli exec vitest run --project uniton603ecf33: VERDICT command-exit 0, Test Files 350 passed (350), Tests 5066 passed (5066). (On98d28ec0it caughtnormalized-call-sites.test.ts: the walk's folded read had no row; added in603ecf33.)pnpm --filter @objectstack/cli typecheckexit 0. The parity file (integration tier): 1 passed on603ecf33.Ablation (each through scripts/ablation-replace.mjs, restored blob == HEAD, git diff HEAD empty)
packages/cli/src/utils/seed-record-values.ts: thevalidateInsertValues(...)call replaced byvoid row;(anchor 1 → 0, blob 1e847578 → 1748ccd5):seed-record-values.test.ts4 failed, 7 passed. Restored.packages/objectql/src/validation/record-validator.ts: thenormalizeMultiValueFields(objectSchema, row, 'include');line deleted (anchor 1 → 0, blob dc438b68 → 16c92c87): the[#27259]block 2 failed, 5 passed (the rewrite case and the no-mutation case). Restored.Dogfood (Clause-② narrowing arm), head 603ecf3
pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood testunder the lock: VERDICT command-exit 0, Test Files 350 passed | 3 skipped (353), Tests 2817 passed | 20 skipped (2837).Derived gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 65 derived families, the same list on603ecf33and on8e7f125e. All 65 run serially asCMD :: exit Non603ecf33; the 9 changeset and closing-keyword families re-run on8e7f125eafter the changeset's kind changed (all exit 0).--ran: 65 accounted, 64 run, 1 NOT-MEASURED, 0 UNRUN.pnpm check:dual-build-cjs-loadsexit 3, PREREQUISITE NOT MET (needs a full build: nodistforembedder-openai,knowledge-ragflow,service-cluster-redis); CI runs it.GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --eventwith this body: exit 0 (Clause-②: no (narrowing),Metadata-protocol: narrowed, minor).98d28ec0caughtcheck:driver-memory-censusexit 1: the parity test bound the frozen@objectstack/driver-memory. It now loads through@objectstack/driver-sqlon SQLite:memory:, and the gate is green on603ecf33.Generated by Claude Code