Skip to content

fix(lint)!: the object publish door refuses an object whose own list view sorts by a field the list door refuses (#30095) - #30564

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-e4pw2l
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-e4pw2l

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#3 · os-bill · session_01Utp6CXQyiKbpV3REQxogrr

Requested by Bill · project thread
Fixes #30095

Clause-②: no (narrowing) — an object write the door accepted before is refused: an object whose built-in listViews.*.sort names a field the list door answers 400 INVALID_SORT now saves 422 INVALID_METADATA.
Metadata-protocol: none
Skills-docs: none — skills/** names no sortable field key, and content/docs/deployment/validating-metadata.mdx lists the doors by rule family, not by reference-integrity member, so it names no object crossing to correct; the prescription ships in the refusal text.

维护者速读

  • 改了什么: 对象自带的列表视图(listViews.*.sort)如果按一个列表门会拒绝的字段排序(字段声明了 sortable: false、是公式字段、或对象根本没有这个字段),对象发布门现在直接拒收,返回 422 INVALID_METADATA,规则名 sort-field-unsortable 或 sort-field-unknown,指到具体排序键路径,不存任何行。
  • 为什么改: 以前 os validate 会报错,视图门和页面门也会拒,唯独对象门不查:Studio 里存对象照样成功,那个列表视图每次打开都答 400 INVALID_SORT。
  • 风险与回滚: 这是收窄:以前能存的这类对象,下次保存会被拒;已存的对象照常加载,另一个对象的写入不会因为已存的违规对象被拒。上线前普查了仓库里全部 127 个对象、102 个列表视图、113 个排序键,0 命中。回滚 = revert 本 PR。
  • 先看: packages/lint/src/reference-integrity-suite.ts 里 validateSortableFields 那一行(runtimeTypes 加了 object)和它上方的 lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves #30095 注释;packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts 末尾的 lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves #30095 用例;packages/lint/src/runtime-gate.object-writes.test.ts 的成员清单钉子。
  • 两分钟验证: 运行 pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.runtime-authoring-gate.test.ts -t 30095,预期 3 个全绿:sortable: false 与不存在的字段各被 422 拒绝、没有存下任何行;对照组按可排序字段保存成功。
  • 需要你做的: 无,席位落地。

What changes

Tests

  • packages/lint/src/runtime-gate.object-writes.test.ts: the member-surface pin now lists validateSortableFields among the members that declare object (it used to assert the opposite and say this is where the adjudication has to be made); three refusals through the door's gate (sortable: false, formula, an unknown name), each one error at objects.leave_request.listViews.inbox.sort[0]; CONTROL: the same list view sorting by a field with no sortable key and by a sortable: true field publishes with the suite run.
  • packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, new top-level describe (lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves #30095), through the real saveMetaItem object publish route: sortable: false refused 422 / INVALID_METADATA, one sort-field-unsortable issue at error at the sort key path, rulesRun names validateReferenceIntegrity, no active row; an unknown name refused the same way as sort-field-unknown; CONTROL: the same object sorting by a sortable field saves one row.

Acceptance notes

Verification

All readings on head 0e5a281c (base c045c341, the #30270 merge).

  • Reproduced before the change (fa9c0aa2 and again on c045c341): through the same saveMetaItem harness, an object whose list view sorts by a sortable: false field saved (success: true, one active row), and so did one sorting by gone_field; validateReferenceIntegrity on the same body reported sort-field-unsortable at error (objects[0].listViews.inbox.sort[0]). The read half (400 INVALID_SORT at the list door for sortable: false) is pinned by packages/rest/src/data-sort-field-sortable-flag-door.test.ts (field.sortable: false has no effect — the sortability projection still reports the field sortable, the data door sorts by it, and the list header and Sort picker offer it #29896).
  • pnpm --filter @objectstack/lint test: Test Files 195 passed (195), Tests 8086 passed (8086). pnpm --filter @objectstack/lint typecheck: exit 0.
  • pnpm --filter @objectstack/metadata-protocol test: Test Files 316 passed, 4 skipped (320); Tests 22434 passed, 26 skipped. pnpm --filter @objectstack/metadata-protocol typecheck: exit 0.
  • pnpm lint: exit 0. pnpm --filter @objectstack/spec check:liveness: exit 0.
  • node scripts/pm/dispatch-gates.mjs --ran: 58 derived, 58 run, 0 NOT-MEASURED, every exit code 0 (four dist-reading gates, check:type-check-debt and the teardown self-test re-run after a full turbo run build --filter='!@objectstack/docs' and a deepening fetch; their first run was exit 3, prerequisite not met, not red).
  • Dogfood (pnpm --filter @objectstack/dogfood test under the verify lock, after building its closure; the title carries !): Test Files 420 passed, 3 skipped (423); Tests 3496 passed, 36 skipped (3532). VERDICT command-exit 0.
  • Ablation (one, on the tested behaviour): scripts/ablation-replace.mjs replaced the member's 'object' with a marker type (ablate30095) in reference-integrity-suite.ts; @objectstack/lint rebuilt; ablation-dist-preflight.mjs found the marker in all 4 built entry files. Result: runtime-gate.object-writes.test.ts 4 failed / 37 passed (the member-surface pin and the three refusals), the door suite -t 30095 2 failed / 1 passed (both refusals red, the object saved; the control green). Restored by the tool (blob equals HEAD, git diff HEAD empty), lint rebuilt, preflight --absent clean on all 20 built files, working tree clean, door suite 3/3 green again.
Gate record (58 derived)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --filter @objectstack/spec check:liveness :: exit 0
pnpm lint :: exit 0

Generated by Claude Code

…#30095)

validateSortableFields now declares `object` in its runtimeTypes, so an
object whose built-in listViews.*.sort names a field the list door refuses
(sortable: false, a formula, or a name the object does not have) is refused
422 INVALID_METADATA at publish instead of saving and failing every load.

Claude-Session: https://claude.ai/code/session_01Utp6CXQyiKbpV3REQxogrr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 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 — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 38d416f1145c2dafbe39f00ecfcbc551dc438bbb → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 528469b486d1eab995ff7bcb7830494fd9fe4cf7 — the merge of head 0e5a281c235dbae3c55dbd09b15f8ec394dffb71 into base 38d416f1145c2dafbe39f00ecfcbc551dc438bbb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 528469b486d1eab995ff7bcb7830494fd9fe4cf7 && git checkout 528469b486d1eab995ff7bcb7830494fd9fe4cf7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 38d416f1145c2dafbe39f00ecfcbc551dc438bbb 0e5a281c235dbae3c55dbd09b15f8ec394dffb71 && git checkout -B drift-repro 38d416f1145c2dafbe39f00ecfcbc551dc438bbb && git merge --no-ff 0e5a281c235dbae3c55dbd09b15f8ec394dffb71

node scripts/docs-audit/affected-docs.mjs --json 38d416f1145c2dafbe39f00ecfcbc551dc438bbb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0e5a281c235dbae3c55dbd09b15f8ec394dffb71
Local-runs: probe — in a throwaway worktree at the head (pnpm install, turbo run build --filter=@objectstack/lint... --filter=@objectstack/metadata-protocol...): vitest run src/runtime-gate.object-writes.test.ts (41/41), vitest run src/protocol.runtime-authoring-gate.test.ts -t 30095 (3 passed, 248 skipped), a reviewer census of every *.object.ts through validateSortableFields (see ①.3), the ablation re-run (member object removed, lint rebuilt: door suite 2 failed / 1 passed, lint pin 4 failed / 37 passed; restored, rebuilt, 3/3 and ["flow","view","page","object"] in dist/index.js and dist/runtime.js), check-adr-0087-registration.mjs --base origin/main exit 0 and check-changeset-no-major.mjs --base origin/main exit 0; worktree removed afterwards.
Scope: packages/lint/src/reference-integrity-suite.ts, packages/lint/src/runtime-gate.object-writes.test.ts, packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts + packages/lint/src/validate-sortable-fields.ts (validateSortableFields, checkSortDeclaration, unsortableFinding, indexObjectSortability), packages/lint/src/reference-integrity-suite.ts (validateReferenceIntegrity dispatch, DEFAULT_MEMBER_RUNTIME_TYPES, the #15254 / #15495 / #14107 member notes), packages/lint/src/authoring-rules.ts (the validateReferenceIntegrity entry, runtimeTypes at line 1259), packages/lint/src/runtime-gate.ts (D4 differential), packages/lint/src/validate-searchable-fields.ts (indexObjectSearchTargets), .changeset/30095-object-list-view-sort-door.md (prose face, read for its declaration lines and marker), content/docs/deployment/validating-metadata.mdx, content/docs/references/api/sortability.mdx, skills/objectstack-ui/rules/list-views.md, .github/workflows/ci.yml (dogfood job condition)

Face: narrowing (Clause-②: no (narrowing) line-initial on the PR body and the changeset). record-recognisers.mjs --paths on the four changed paths decides no schema or governed face, so the narrowing face alone sets the tier; the Scope floor is every path of the diff outside the prose faces. Review by an isolated reader of PR #30564 (card #30095, precedent PR #30270 / card #30093, records 6649790600 and 6650369794). Reads: the card body, the PR body, the dev report (comment 6662809673, read for its claims only), git diff c045c341..0e5a281c (4 files, +176/−4; base c045c341 is the #30270 merge; git merge-tree against origin/main at a4c3e738 merges clean, the only origin/main drift on cited files being #30231's runtime-gate.ts dataset-context hunk and a six-line validating-metadata.mdx paragraph on the same), the files in Scope: at this head, the head's check-runs via REST (three readings, last at 2026-11-05T16:22Z). No reading below comes from the dev's self-report; where the report's number is quoted it is beside my own.

① Derived judgments

  1. What the object publish door newly refuses (saveMetaItem for type: 'object' going state: 'active', and the draft→active promotion; Studio, PUT /api/v1/meta/object/NAME, MCP). With object in the member's runtimeTypes, validateReferenceIntegrity (writeType === 'object') now runs validateSortableFields on the per-write snapshot, whose first rung walks objects[].listViews.*.sort and resolves each key against the list view's data.object retarget or its own object. Three verdicts, all error and therefore refused 422 INVALID_METADATA with nothing stored: (a) a sort key naming a field that declares sortable: false → sort-field-unsortable (author-declared, via resolveObjectSortability, the same projection the list door refuses by); (b) a sort key naming a virtual formula field → sort-field-unsortable (virtual-type); (c) a sort key whose head segment is neither a declared field nor a SYSTEM_FIELDS name → sort-field-unknown — unknown names are refused too, dotted paths included ('sort' reaches only … own columns hint), exactly as the card's title under-states ("sortable:false") and the PR body states. Not refused: a sort-field-unprovisioned finding on an injected anchor (warning, advisory, not gating); a list view retargeted to an object the snapshot does not carry or an external/introspected object (fieldsByObject miss → silent, the [finding] Both field-resolution gates for a list view (searchableFields and sort) are CLI-only — the reference-integrity suite dispatches at the runtime publish gate for flow alone, so a Studio/MCP view write gets neither #9313 missing-collection channel stays closed); a ?mode=draft save (never gated, unchanged). Right, and the PR body's and changeset's description of the refused set is exact.
  2. The CLI already refused all of it. validateReferenceIntegrity skips a member only when options.runtimeWriteType is set (reference-integrity-suite.ts, the writeType !== undefined && …continue guard); os validate / os build / os lint pass none, so every member — this one included — has run on an object's listViews.*.sort since A view's declared sort naming a formula field clears lint, then 400s on every load — the SORT axis has no authoring gate, unlike SEARCH (#6674) #9257 (existence, virtuality) and on sortable: false since fix(lint)!: refuse a stored sort on a sortable: false field, on list views and every stored page sort #30084 (merged 3a272086). The view and page doors already dispatched the member ([finding] Both field-resolution gates for a list view (searchableFields and sort) are CLI-only — the reference-integrity suite dispatches at the runtime publish gate for flow alone, so a Studio/MCP view write gets neither #9313, lint: a view sort naming a field the author declared sortable: false clears validate, then 400s on every load once #29896 lands — validate-sortable-fields judges only the virtual predicate #30023). Right: nothing moves at the CLI; the object door catches up to the verdict the other three doors give.
  3. Census (reviewer's own, through validateSortableFields({ objects }) with every object as context, at the head): 122 *.object.ts under packages/ and examples/; 126 objects loaded (one file, packages/plugins/plugin-audit/src/objects/sys-activity.object.ts, did not import in my partial build and was read by hand: its two list views both sort timestamp, a declared Field.datetime, so 127 objects in all, matching the dev's count); 44 objects carry 100 built-in list views with 111 sort keys (+1/+2/+2 from the hand-read file = the dev's 45 / 102 / 113); 0 findings. Synthetic probe (a list view sorting by a sortable: false field) draws error:sort-field-unsortable, so the zero is non-vacuous. grep corroboration: no *.object.ts under packages/ or examples/ declares sortable: false, and no object file with a formula field also carries a sort:; packages/qa and examples hold no inline sortable: false fixture outside tests. No object in the repo or its examples is newly refused.
  4. Public surface. No export is added, removed or retyped in @objectstack/lint; REFERENCE_INTEGRITY_RULES changes one member's runtimeTypes value. @objectstack/lint publishes dist (private unset), so a changeset is owed and present. @objectstack/metadata-protocol is touched only in a test file (files: ["dist", …] ships none of it), so no changeset is owed there — the same reading the fix(lint)!: refuse a stored page sort naming an unknown field, on every page sort slot (#30093) #30270 record made. Right.
  5. Snapshot safety. The member resolves only against stack.objects (indexObjectSearchTargets, indexObjectSortability), the collection every per-write snapshot carries; its views[] and pages[] rungs read stack.views / stack.pages behind Array.isArray guards, so on an object snapshot they contribute nothing. The gate's differential ([runtime/metadata] 作者时规则只存在于 CLI:Studio/REST/MCP 的运行时授权面是第四扇门,26 条规则一条不跑——#4409 修完后最大的敞口 #4463 D4, structural in runtime-gate.ts) keeps a stored sibling's violation off this write. The authoring-rules.ts entry already names object (line 1259), so no entry edit is owed — right, authoring-rules.ts untouched is correct.
  6. Tests. runtime-gate.object-writes.test.ts: the crossed roster pin now lists validateSortableFields (the former not.toContain assertion and its "this pin is where the adjudication has to be made" note are removed, which is the adjudication being made); three refusals through runRuntimeAuthoringRules at objects.leave_request.listViews.inbox.sort[0] with rule and message asserted; CONTROL publishes on a field with no sortable key and on sortable: true, and asserts rulesRun contains validateReferenceIntegrity (non-vacuity). protocol.runtime-authoring-gate.test.ts lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves #30095 describe goes through the real protocol.saveMetaItem({ type: 'object' … }) route: status 422, code INVALID_METADATA, rule / severity / path / message, rulesRun, and no active row; the unknown-name case the same way; CONTROL saves one row. code and status are both asserted (ADR-0112 envelope). Ablation reproduced by me (Local-runs). Right.
  7. Declarations (PR body and changeset, both line-initial): Clause-②: no (narrowing) — right: an accept set at a published door narrows, nothing widens. Metadata-protocol: none — right, ruling in ③. Skills-docs: none — REASON — right, reading in ③. Title carries !, changeset carries the BREAKING banner, one ADR-0087 marker not-required (no-migration-prescription): the gate accepts it (exit 0, see Local-runs) and its reason is true at this head — no schema key moves, and a refused sort never ordered a row (the list door answered 400 INVALID_SORT on every load, pinned by packages/rest/src/data-sort-field-sortable-flag-door.test.ts field.sortable: false has no effect — the sortability projection still reports the field sortable, the data door sorts by it, and the list header and Sort picker offer it #29896), so no FROM → TO rewrite exists to prescribe. Observation, no action: fix(lint)!: refuse a stored sort on a sortable: false field, on list views and every stored page sort #30084 cited the registered entry field-sortable-false-sort-refused for the same value; either not-required reading is defensible here.
  8. Card closure. Fixes #30095 is right: the card's three asks (member declares object with its own rollout decision; refusal pinned through the real object publish route; the lint suite member pin in the same PR) are all in this diff. First body line references the card; target main; draft; head repo = base repo.

② Semver level

@objectstack/lint: patch, fix(lint)!: title, BREAKING banner, both declaration lines and one ADR-0087 marker in the body — consistent with AGENTS.md Post-Task Checklist step 3 under Metadata-protocol: none: "a non-protocol change, widening or breaking, ships patch and keeps its BREAKING banner and ADR-0087 disposition"; check-changeset-no-major.mjs ruling A text says the same ("A non-protocol … narrowing … ships patch and keeps its BREAKING banner"). Were the line narrowed, minor would be owed instead — the dependency the dev's open question names, decided in ③. Check Changeset succeeded on this head; the two local changeset gates exit 0.

③ Boundary flags

④ Design judgment (narrowing face)

The narrowing is judged against the published spec on main: does it take a declared capability away at the consumer, or make the spec's own refusal real? It makes the spec's refusal real. resolveObjectSortability (@objectstack/spec/api) is the served verdict the Data API list door refuses by (assertSortFieldsExist, 400 INVALID_SORT, #29896 / #30023), and the lint member reads the identical projection (indexObjectSortability), so an object the door now refuses is one whose list view could never have loaded; no author loses a sort that worked. The crossing completes the #14107 "identical rungs" invariant — the three list-view siblings now all judge an object's built-in list views at the object door, where before the door judged a list view's columns and searchableFields but not its sort. The #9313 missing-collection argument holds for this member exactly as it did for its two siblings in #15495 (resolution against stack.objects only; absent rungs, not dead rungs). The per-member runtimeTypes discipline is respected: declaration + measurement + roster pin + note, no "all types" shortcut. The refusal text carries the prescription (which field, why, and Sort by another field, or remove \sortable: false`), so the author at the only door Studio has gets the same sentence os validate` gives. Nothing in the diff reaches past the member and its pins. Holds.

Must-fix: none. Should-fix: S1 (docs row, separate card). Observations: ①.7 marker category.

Implemented-by: claude/project-thread-e4pw2l
Reviewed-by: session_01Utp6CbPxYq1wQmUNWvMZkC

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

维护者速读(席位终稿)

  • 改了什么:对象发布门(Studio、PUT /api/v1/meta/object/NAME、MCP)现在也跑列表视图排序检查。对象自己的 listViews.*.sort 按 sortable: false 字段、formula 字段或不存在的字段排序时,发布被 422 INVALID_METADATA 拒收,什么都不存;正确的排序照常发布。
  • 为什么改:CLI、view 门、page 门早就拒收这三种排序,只有对象门放行;放进去的列表视图每次加载都被列表门回 400 INVALID_SORT(lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves #30095)。
  • 风险与回滚:收窄(BREAKING):以前能从对象门发布的这类坏排序,下次发布被拒;草稿、别的包或外部对象不判,sort-field-unprovisioned 仍只是警告。仓库与示例里 127 个对象、113 个排序键实测 0 命中。CLI 结论不变,所以 Metadata-protocol: none、lint 走 patch。回滚即 revert。
  • 先看:packages/lint/src/reference-integrity-suite.ts 里 validateSortableFields 成员的 runtimeTypes 加了 object;packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts 的 #30095 一节。
  • 两分钟验证:pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.runtime-authoring-gate.test.ts -t 30095,3 条全过。
  • 需要你做的:无。技能定档:非受管面、180 行,不需人审;契约复核 6663292380(Head-sha 0e5a281c235dbae3c55dbd09b15f8ec394dffb71,PASS),席位落地。

席位意见:同意落地。只让对象门补上其余三道门早已给出的结论,全仓实测零误伤。

席位核对

ACCEPT · head 0e5a281c235dbae3c55dbd09b15f8ec394dffb71 · card #30095 · 2026-11-05T16:35Z.

  • Shape: draft PR to main, relay author, Fixes #30095 only. 4 files, +176 −4. check-governed-merges.mjs --pr 30564 → exit 0, NOT governed, 180 ≤ 3000. No merge=os-regen path. Merge-tree against origin/main clean (review 6663292380).
  • Premise measured: the reviewer reproduced the refusal through the real saveMetaItem({ type: 'object' }) route (3/3) and the ablation (door suite 2 failed / 1 passed, lint pin 4 failed / 37 passed with object removed); census at the head: 127 objects, 102 list views, 113 sort keys, 0 findings, synthetic probe non-vacuous.
  • Gates (check-runs on this head): 63 runs, 54 success, 9 path-gated skipped, 0 failed, 0 in progress (read 2026-11-05T16:35Z).
  • Dogfood battery (! + narrowing, pinned to this head): 0e5a281c · os-verify-lock VERDICT command-exit 0 · Test Files 420 passed, 3 skipped (423) · Tests 3496 passed, 36 skipped (3532) · no failures (dev report 6662809673; dogfood shards do not run on pull_request).
  • Accepted deviation: none.
  • Out of scope: should-fix S1 (four-doors table in validating-metadata.mdx stale before this PR) filed as docs: the four-doors table in validating-metadata.mdx under-reports the reference-integrity row and has no row for the list-view sort/search members #30574. Skills-docs: none — REASON confirmed by the review.
  • Landing: BREAKING (!, Clause-②: no (narrowing)), not governed, ≤ 3000 ⇒ the skill requires no human merge; maintainer 13:47Z 「技能不需要我审核的不应该等我」 ⇒ seat lands it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lint: the object publish door does not run validateSortableFields, so an object whose listViews sort a sortable:false field saves

1 participant