Repository navigation
fix(lint)!: the remaining field indexes judge an object as served, and an object that declares no field keeps the list-view skip once extended (#30608) - #30659
Conversation
…d an object that declares no field keeps the list-view skip once extended (#30608) The form-layout, notify email-field recipient, public form file-field, flow template, page visualization binding, searchable-fields and autonumber indexes read objectsAsServed: a field an objectExtensions entry adds is known, a re-declared field is judged on the extension's definition, a tied field is a served name judged on no definition, and an object that declares no field stays unjudged where main skipped it. A react page's <ListView searchableFields> reads the same served search index. The list-view rule asks its declares-nothing skip of the declared object. Claude-Session: https://claude.ai/code/session_01L6bhgVSM5tJd2UYk3Aa1MM Co-authored-by: Claude <[email protected]>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 4 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 ea8d0fc9fd7f3f9dac1d29cd52930a75b36a4ab4 && git checkout ea8d0fc9fd7f3f9dac1d29cd52930a75b36a4ab4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a2437b560bd8872feece6ec8b57c6fdf21cff1f4 887e562308b62646ad2d87b3b11a35050e88317b && git checkout -B drift-repro a2437b560bd8872feece6ec8b57c6fdf21cff1f4 && git merge --no-ff 887e562308b62646ad2d87b3b11a35050e88317b
node scripts/docs-audit/affected-docs.mjs --json a2437b560bd8872feece6ec8b57c6fdf21cff1f4
|
|
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flagsDev report 6670038297:
Elsewhere in the report: the ablation tool's two refusals ("replacement already present", re-run) leave no trace in the diff; ④ Design judgmentJudged against the scenario of record — an app package's object extended by a module package's
Must-fix: none. Should-fix: (1) Gate verdicts on this head, read at 2026-11-05T21:50Z after every run had completed (the last landed one minute before the read): 63 check-runs ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…n all-tied object still refuses an unknown entry (#30608) A searchable target built from the tie-dropped objects was null when every served field was tied, which turned the existence check off. Names now come from the served objects with ties kept, and the tied metas are blanked afterwards. The changeset also records two moves the review measured: a flow variable named after an extension-added trigger field is an ambiguous root, and an extended owner's legacy array fields are known names to form layout. Claude-Session: https://claude.ai/code/session_01L6bhgVSM5tJd2UYk3Aa1MM Co-authored-by: Claude <[email protected]>
|
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P Contract review — deltaServed-tier: ① Derived judgments
② Semver levelUnchanged: ③ Boundary flagsDev report 6671358427: ④ Design judgment
Must-fix: none. Should-fix: none new. Carried: should-fix 1 of 6670491743 to the family's card (the seat's disposition); should-fix 4 (the sister card) with the dev's measurement attached. Gate verdicts on this head as they stand, read at 2026-11-05T22:41Z (the push is minutes old): 63 check-runs ( Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P 维护者速读
席位核对ACCEPT · head
Generated by Claude Code |
Requested by Sales · project thread
domain:spec#2 · os-sales · session_01L6bhgVSM5tJd2UYk3Aa1MM
Fixes #30608
Clause-②: yes (narrowing) —
os validate/os build/os lintrefuse shapes they accepted before, each a field anobjectExtensionsentry of the same artifact adds or re-declares, now judged on its served definition: a notify node'srecipients.emailFieldnaming an owneremailfield an extension re-declares astextisnotify-email-field-not-emailaterror; a form open to anonymous intake whose section names animage/filefield an extension adds, or an owner field an extension re-declares as one, ispublic-form-file-fieldaterror; alistpage leadingallowedVisualizationswithgalleryover an ownerimagefield an extension re-declares astextispage/visualization-without-bindingaterror; asearchableFieldsentry naming an owner text field an extension re-declares asformulaissearchable-field-unsearchableaterror; an autonumber field an extension adds whose format names a field the object lacks isautonumber-references-unknown-fieldaterror, and a format naming an ownerrequired: truefield an extension re-declares without it isautonumber-references-optional-fieldatwarning; a flow template{{ record.F.sub }}through an owner text field an extension re-declares as a lookup isflow-template-lookup-traversalatwarning. The widening half: a form section, a notify email-field recipient, a flow template hole, a gallery or calendar binding, asearchableFieldsentry (an object's own set, a list view's narrowing, a react page's<ListView searchableFields>) or an autonumber format naming a field anobjectExtensionsentry adds is no longer refused as unknown, and a list view over an object that declares no field is no longer refused once an extension adds to it.Metadata-protocol: narrowed
Skills-docs: none — measured at
2818af5b:git grepof the twelve moved rule ids overskills/andcontent/docs/hitspublic-form-file-field(content/docs/ui/forms.mdx:258,content/docs/ui/public-data-collection.mdx:55,skills/objectstack-api/SKILL.md:106),searchable-field-unknown/searchable-field-unsearchable(content/docs/data-modeling/schema-design.mdx:153,172,content/docs/ui/views.mdx:182-184,skills/objectstack-data/SKILL.md:128,154,skills/objectstack-ui/rules/list-views.md:207-208) and release pages only otherwise; none states a declared-only reading ("a file field", "a renamed / mistyped column"), so every sentence reads true. The patch round (887e5623) adds no rule id and changes no message.维护者速读
objectExtensions加的字段;React 页的<ListView searchableFields>也读同一份;列表视图规则对“一个字段都没声明的对象”恢复不判。packages/lint/src/validate-searchable-fields.ts的indexServedSearchTargets(名字从带并列的 served 对象取、并列字段的定义置空占位、不声明字段对象的 null);packages/lint/src/validate-page-visualization-bindings.ts的 skip 2 与tiedOnObject;packages/lint/src/object-graph.test.ts末尾的[#30608]一组。pnpm --filter @objectstack/lint exec vitest run src/object-graph.test.ts -t 30608,预期 42 例全绿;其中 moved 表 14 行各自断言 main 视角(无扩展)的旧结论和现在的新结论。What changes
One artifact, two legs (the union artifact and the per-package body, identical on every row): an app package declares
xs_deal(name, plus the owner field a row names) andxs_account; a module package extendsxs_deal.validate-form-layout.tsfield-name setobjectsAsServed; tie name kept (existence); an unextended object keeps its map-only readnowherebyte-for-bytevalidate-notify-email-field-recipients.tsindexFieldTypesnull(skipped); declares-nothing keeps its empty mapvalidate-public-form-file-fields.tsindexFieldTypesvalidate-flow-template-paths.tsfield mapvalidate-page-visualization-bindings.tsderivationvalidate-searchable-fields.tsindexServedSearchTargets: names read off the served objects with ties kept, then each tied field's meta blanked to a stub (so an object whose every served field is tied keeps a target and still refuses an unknown entry), declares-nothing keptnull;validateSearchableFieldsreads it;indexObjectSearchTargetsstays the declared primitive (docblock says why)validate-react-page-props.ts<ListView searchableFields>indexServedSearchTargets(file surface amended by the seat)lint-autonumber-formats.tsvalidate-list-view-field-refs.ts(item 2)nullwhen extendedEvery finding that moves, against
mainMeasured with
runAuthoringRules('validate', { normalized, parsed })onorigin/maina8390207and on2818af5b, union and per-package legs (identical on every case); 58 cases, every unlisted case unchanged (the no-extension controls and everynowherecontrol included). The last two rows were measured in the patch round ona8390207and887e5623(the rule sources of each, throughvalidateFlowTemplatePaths/validateFormLayout, both legs identical).mainform-field-unknownwarningemailFieldnaming an addedemailfield (also on a tie)notify-email-field-unknownerroremailFieldnaming an addedtextfieldnotify-email-field-unknownerrornotify-email-field-not-emailerroremailFieldnaming an owneremailfield re-declaredtextnotify-email-field-not-emailerrorimagefieldform-field-unknownwarning)public-form-file-fielderrortextfield re-declaredfilepublic-form-file-fielderrorfilefield re-declaredtextpublic-form-file-fielderror{{ record.region }}, added field (also{{ record.account.name }}on a tie)flow-template-unknown-fieldwarning{{ record.account.name }}, ownertextre-declared lookupflow-template-lookup-traversalwarning{{ record.account.name }}, owner lookup re-declaredtextflow-template-lookup-traversalwarningimagefield (also on a tie)page/visualization-without-bindingerrorimagefield re-declaredtextpage/visualization-without-bindingerrorsearchableFields(object's own, list view's) naming an added field (also on a tie)searchable-field-unknownerrorsearchableFieldsnaming an ownertextfield re-declaredformulasearchable-field-unsearchableerror{region}{000}, addedrequiredfield (also on a tie)autonumber-references-unknown-fielderror{region}{000}, added optional fieldautonumber-references-unknown-fielderrorautonumber-references-optional-fieldwarning{plan}{000}, ownerrequiredfield re-declared optionalautonumber-references-optional-fieldwarningautonumber-references-unknown-fielderrortextautonumber-references-unknown-fielderrorxs_empty(declares no field), extended (item 2)list-view-field-unknownerrorsearchableFieldslist-view-field-unknownerrorform-field-unknownwarningmain)partnerbound to anxs_accountrecord ({{ partner.nowhere }}), where the extension adds apartnerfield to the trigger objectflow-template-unknown-fieldwarningpartneris an ambiguous root, as a variable named after a declared trigger field is; a variableacctis still judged)legacy, an owner field declared in the legacy arrayfieldsshape, on an extended objectform-field-unknownwarningform-field-unknownwarning)Verification
Patch round (head
887e5623)The contract review (6670491743) asked for one code fix and two declarations.
887e5623:indexServedSearchTargetsreads names off the served objects with ties kept and blanks the tied metas afterwards, so an object whose every served field is tied (the owner declares onlytitle, two packages re-declare it) still refusessearchableFields: ['nowhere']asmaindoes; the changeset's Now / Not changed name the flow-variable root and the legacy array-shape moves (the last two rows of the table above). Same basea8390207,NODE_OPTIONS=--max-old-space-size=4096, heavy runs underos-verify-lock.pnpm --filter @objectstack/lint test && pnpm --filter @objectstack/lint typecheck: Test Files 195 passed (195), Tests 8216 passed (8216);check:test-typecheck: OK; VERDICT command-exit 0. The[#30608]describe: 42 passed;object-graph.test.ts200 passed.pnpm --filter @objectstack/dogfood testunder the lock: VERDICT command-exit 0; Test Files 420 passed, 3 skipped (423); Tests 3504 passed, 36 skipped (3540).npx eslint --no-inline-configover the 2.tsfiles the round changes: exit 0.dispatch-gates --commandsat887e5623: the same 57 commands as at2818af5b, each run as printed, all 57 exit 0 (the sevendist/from the first round still present).dispatch-gates --ranover<command> :: exit <code>lines: "57 derived, 57 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero, exit 0.check-changeset-no-major --base origin/main --event EVENTwith this body as thepull_requestpayload: exit 0.scripts/ablation-replace.mjs, the target built from the tie-dropped objects with the tied names re-added (the2818af5bcode, inlined): 1 failed / 199 passed (the all-tied pin), restored, blob equal to HEAD,git diff HEADempty.origin/mainis atb77c160a; not merged.First round (head
2818af5b)At head
2818af5b(baseorigin/maina8390207),NODE_OPTIONS=--max-old-space-size=4096, heavy runs underos-verify-lock.pnpm --filter @objectstack/lint test && pnpm --filter @objectstack/lint typecheck: Test Files 195 passed (195), Tests 8210 passed, 5 skipped (8215);check:test-typecheck: OK; VERDICT command-exit 0. The[#30608]describe: 41 passed;object-graph.test.ts199 passed.(narrowing)arm owes it): the closureturbo run build --filter='@objectstack/dogfood^...'(VERDICT command-exit 0), thenpnpm --filter @objectstack/dogfood testunder the lock: VERDICT command-exit 0; Test Files 420 passed, 3 skipped (423); Tests 3504 passed, 36 skipped (3540).npx eslint --no-inline-configover the 10 changed.tsfiles: exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat2818af5b: 57 derived commands, each run as printed with its exit code recorded. First pass: 56 exit 0 andpnpm check:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET: seven packages with nodist/: studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, service-cluster-redis, service-knowledge); after building those seven under the lock it exits 0 (116 entry points across 71 packages load).check-plugin-teardown-shape.mjs --self-testran after fetching its fixture commit621a4876.dispatch-gates --ran: "57 derived, 57 run, 0 NOT-MEASURED, 0 UNRUN", exit 0.check-adr-0087-registration --base origin/main,check-changeset-no-major --base origin/mainandcheck-empty-changeset --base origin/main: exit 0.origin/mainmoved tob8929f48after the base (one spec docblock commit, nopackages/lintfile); not merged.Ablations (each through scripts/ablation-replace.mjs: anchor hit once, object-graph.test.ts run, restored, blob equal to HEAD, git diff HEAD empty)
The lint tests import
src(relative imports, no alias todist), so no build sits between the mutation and the run. Back to the declared index, per site:served = declared→ 1 failed / 198 passed (the existence row).objectsAsServed→ declared objects, no ties → 4 failed (existence, two moved rows, tie).served = declared→ 4 failed (three moved rows, tie).indexServedSearchTargets→indexObjectSearchTargets→ 4 failed (two existence rows, moved row, tie).served = declared→ 6 failed (existence, four moved rows, tie).Ties and declares-nothing guards, one leg each: page visualization tie guard off, notify / flow template / autonumber tie read as a definition, searchable tie stub dropped → 1 failed each (that site's tie row); notify, public form, page visualization (skip 2 on the served object), searchable (declared
nulldropped) and autonumber declares-nothing guards off → 1 failed each (that site's declares-nothing row). Two first attempts were refused by the tool (the replacement text already occurred in the file) and re-run with distinct replacements; both then went red.Acceptance notes
searchableFieldsrefusal on an extension field unless the code shows the runtime search reads extension fields there. It does: the ingress gate'sresolveQueryFields(packages/metadata-protocol/src/protocol-query-validation.ts:341) readsregistry.getObject(object), which resolves throughfoldExtendersOntoDefinition(packages/objectql/src/registry.ts:6476), and the engine's search expansion reads the same schema. The seat re-read it onmainand amended the file surface tovalidate-react-page-props.ts(6669203452); the PR fix(lint)!: the remaining field-name indexes judge an object as served, so a read or write of a field an objectExtensions entry adds is not refused (#30471) #30533 pin is flipped in place.indexObjectSearchTargetsstays declared. It is exported, the sort rule composes it (it hands it pre-served objects and reads itsnulloff the declared ones), and out-of-repo callers read it; the served reading is a new function beside it.mainand still do, extended or not, as the write, readonly and sort rules do after lint: the field-name indexes outside indexObjectGraph (validate-expressions, indexObjectFields readers, hook-body-writes, sortable-fields, translation-references) still read declared objects only, refusing extension-added fields #30471. The form-layout and flow template rules judged such an object against the empty set onmain(every entry reported); they still do, with the extension's names now known. Moving them to a skip would be a further widening outside this card.image/filetype on an object that declares fields, which is where its new refusals come from; onmainsuch a field was the form-layout rule'sform-field-unknownwarning.indexServedObjectGraphcallers (sister card, carrier: seat). Measured in the patch round on builtdist/(pnpm --filter @objectstack/lint buildof each commit'spackages/lint/src, every other workspace package at this head),runAuthoringRules('validate', { normalized })afternormalizeStackInput, union and per-package legs identical:xs_emptydeclaresfields: {}, an extension addsregion, a record page'srecord:highlightsbinds['region', 'nowhere']and a dataset's dimensions namenowhereandregion.validate-dataset-references(anindexServedObjectGraphcaller): extended →dataset-field-unknownerror,dimension field "nowhere" is not a field on object "xs_empty".; unextended → nothing (the declarednullskip). The same on887e5623, onorigin/mainb77c160aand on2046d21b(482e089a^, before PR fix(lint)!: the remaining field-name indexes judge an object as served, so a read or write of a field an objectExtensions entry adds is not refused (#30471) #30533): the extended object is judged on the extension's fields alone, so the gap predates fix(lint)!: the remaining field-name indexes judge an object as served, so a read or write of a field an objectExtensions entry adds is not refused (#30471) #30533.validate-page-field-bindings(readsobjectsAsServeddirectly): extended →page-field-unknownwarning,field "nowhere" is not a field on object "xs_empty" — the component silently skips it, so it never renders.; unextended → the same warning forregionand fornowhere. The same on all three commits: this rule has no declares-nothing skip at all, so it judges such an object against the empty set extended or not (the form-layout reading), and the extension only takesregionout of the report.rule-explanations.tsis not changed: none of the moved ids' explanations states a declared-only reading.viewwrite for form layout and public form, anobjectwrite for autonumber) hand the rules a snapshot; this PR measured the CLI legs only.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgVSM5tJd2UYk3Aa1MM