Repository navigation
Commit c133cfe
domain:server#3 · os-bill · session_01Utp6CXQyiKbpV3REQxogrr
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgJrQp25DwmU8PpHXY7H6t15)_
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`)和它上方的 #30095
注释;`packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`
末尾的 #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
- **`validateSortableFields` declares `object` in its `runtimeTypes`**
(`packages/lint/src/reference-integrity-suite.ts`). The
`validateReferenceIntegrity` entry already dispatches on `object` writes
(#15254); the member axis decides which members judge that snapshot, and
this member was the one list-view sibling #15495 held back for its own
adjudication. Its first rung walks `objects[].listViews.*.sort`, so the
object publish door (`PUT /api/v1/meta/object/NAME`; Studio, REST, MCP)
now gives an object's own list view sort the verdicts `os validate`, the
`view` door and the `page` door already give it: `sort-field-unsortable`
(`sortable: false`, a virtual `formula`) and `sort-field-unknown`, both
`error`, refused `422 INVALID_METADATA`, nothing stored.
- **Why it is safe (the #15254 / #9313 terms).** The member resolves
only against `stack.objects`, the collection every per-write snapshot
carries, so it has no missing-collection false-positive channel; its
`views[]` and `pages[]` rungs find nothing on an object snapshot. The
gate's differential (#4463 D4) still never charges a stored sibling's
violation to this write.
- **Rollout measurement, before crossing** (base `c045c341`, through the
door's own shape: `runRuntimeAuthoringRules`, type `object`, every other
corpus object as context): 122 `*.object.ts` files under `packages/` and
`examples/`, 127 objects, 45 of them carrying 102 built-in list views
with 113 sort keys — **0 findings, precision 1.0**. The same sweep's
synthetic probe (a list view sorting by a `sortable: false` field) drew
`sort-field-unsortable` from the rule and nothing from the door: the
defect, measured. The member note carries the population.
- `authoring-rules.ts` is not touched: the entry already lists `object`.
## 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` (#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
- `Metadata-protocol: none` is the claim's line, copied. Reading for the
seat: nothing an author may write changes, because `os validate` / `os
build` / `os lint` already refused this sort; only the runtime door's
accept set narrows, which the `(narrowing)` arm and the BREAKING banner
carry. #30270 declared `narrowed` because it moved the CLI verdict too.
- Draft saves (`?mode=draft`) are not judged, unchanged (#4716 D1).
## 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` (#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.
<details><summary>Gate record (58 derived)</summary>
```
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
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CXQyiKbpV3REQxogrr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2ffdb64 commit c133cfe
4 files changed
Lines changed: 176 additions & 4 deletions
File tree
- .changeset
- packages
- lint/src
- metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
298 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
299 | 330 | | |
300 | 331 | | |
301 | 332 | | |
| |||
336 | 367 | | |
337 | 368 | | |
338 | 369 | | |
| 370 | + | |
339 | 371 | | |
340 | 372 | | |
341 | 373 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
94 | 106 | | |
95 | 107 | | |
96 | 108 | | |
| |||
278 | 290 | | |
279 | 291 | | |
280 | 292 | | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
281 | 298 | | |
282 | 299 | | |
| 300 | + | |
283 | 301 | | |
284 | 302 | | |
285 | 303 | | |
286 | 304 | | |
287 | 305 | | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | 306 | | |
292 | 307 | | |
293 | 308 | | |
| |||
336 | 351 | | |
337 | 352 | | |
338 | 353 | | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
339 | 387 | | |
340 | 388 | | |
341 | 389 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5531 | 5531 | | |
5532 | 5532 | | |
5533 | 5533 | | |
| 5534 | + | |
| 5535 | + | |
| 5536 | + | |
| 5537 | + | |
| 5538 | + | |
| 5539 | + | |
| 5540 | + | |
| 5541 | + | |
| 5542 | + | |
| 5543 | + | |
| 5544 | + | |
| 5545 | + | |
| 5546 | + | |
| 5547 | + | |
| 5548 | + | |
| 5549 | + | |
| 5550 | + | |
| 5551 | + | |
| 5552 | + | |
| 5553 | + | |
| 5554 | + | |
| 5555 | + | |
| 5556 | + | |
| 5557 | + | |
| 5558 | + | |
| 5559 | + | |
| 5560 | + | |
| 5561 | + | |
| 5562 | + | |
| 5563 | + | |
| 5564 | + | |
| 5565 | + | |
| 5566 | + | |
| 5567 | + | |
| 5568 | + | |
| 5569 | + | |
| 5570 | + | |
| 5571 | + | |
| 5572 | + | |
| 5573 | + | |
| 5574 | + | |
| 5575 | + | |
| 5576 | + | |
| 5577 | + | |
| 5578 | + | |
| 5579 | + | |
| 5580 | + | |
| 5581 | + | |
| 5582 | + | |
| 5583 | + | |
| 5584 | + | |
| 5585 | + | |
| 5586 | + | |
| 5587 | + | |
| 5588 | + | |
| 5589 | + | |
| 5590 | + | |
| 5591 | + | |
| 5592 | + | |
| 5593 | + | |
| 5594 | + | |
| 5595 | + | |
| 5596 | + | |
| 5597 | + | |
| 5598 | + | |
| 5599 | + | |
| 5600 | + | |
| 5601 | + | |
| 5602 | + | |
| 5603 | + | |
| 5604 | + | |
| 5605 | + | |
| 5606 | + | |
0 commit comments