Uh oh!
There was an error while loading. Please reload this page.
feat(spec,rest): serve a per-column sortability projection with object metadata - #11214
Conversation
…t metadata Fixes#10235 (spec/rest leg; consumer leg is objectui#5729). 2026-08-23 ruling, option A: the platform serves an explicit per-column sortability signal with object metadata; the grid reads the signal and never re-derives 'virtual => unsortable' from field type. - packages/spec/src/api/sortability.zod.ts: ObjectSortabilitySchema + resolveObjectSortability, derived from the spec's own storage predicates (isVirtualSearchField / SEARCH_VIRTUAL_TYPES, unprovisionedInjectedColumns) so the signal cannot drift from what the runtime doors (#6994/#7095) refuse. The closed category set is enumerated in the module docblock. - GetMetaItemResponseSchema gains an optional envelope-level 'sortability' key (never inside the document: FieldSchema is strict and the key must stay un-authorable). - rest: computed in translateMetaEnvelope - the one seam every single-item read exit passes through - so the cached (default) branch serves it too, from the final post-masking document. - Tests: spec resolver pins (predicate-coupled via SEARCH_VIRTUAL_TYPES loop, summary/autonumber anti-overreach, anchor caveat), rest branch/agreement pins, and the shipped-corpus oracle pins in app-crm / app-showcase (crm_opportunity.expected_revenue x2 views, showcase_project.budget_remaining). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RadETjNRLALFLhFA3xehZP
…r the sortability projection - FieldSortability / ObjectSortability flip to z.input (bare = author state) and join the isomorphic pin registry (no defaults/transforms by design). - quick-reference API Protocol page total 28 -> 29 (the new reference page). - rest test double now folds the plural URL spelling like the real producer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RadETjNRLALFLhFA3xehZP
📓 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: ⛔ 3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 128 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 362f1b25927ef6374286ee344ff449944a6808b0 && git checkout 362f1b25927ef6374286ee344ff449944a6808b0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d25f700735373f65ec86bc009fc87034ff6efa54 f87dbf113156220004ef3aaa1253a5a214f60fc8 && git checkout -B drift-repro d25f700735373f65ec86bc009fc87034ff6efa54 && git merge --no-ff f87dbf113156220004ef3aaa1253a5a214f60fc8
node scripts/docs-audit/affected-docs.mjs --json d25f700735373f65ec86bc009fc87034ff6efa54
|
…c errors The @objectstack/rest test layer is gated by a shrink-only TEST_DEBT ledger (155 raw tsc errors); the new test file added 2 (TS2835 missing .js extension, TS2550 Array.prototype.at under the repo lib). Explicit extension + indexed access; re-measured 157 -> 155 with the ledger's own synthetic project. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RadETjNRLALFLhFA3xehZP
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10235
Spec/rest leg only — the consumer leg (grid drops the sort affordance where the signal says unsortable; the personalization PUT is never offered) is objectstack-ai/objectui#5729, which remains open and is Blocked-by this PR. objectstack-ai/objectui#5729 is not addressed here.
Maintainer ruling (2026-08-23, option A, recorded on the issue): the platform serves an explicit per-column sortability signal with object metadata; the grid reads the signal and never re-derives "virtual ⇒ unsortable" from field type.
Deliverable 1 — the closed "unsortable" category set, enumerated from the door predicates
Judged from what the two runtime doors (
assertSortFieldsExist, metadata-protocol #6994;assertOrderByIsMaterializable, objectql #7095) and the lint predicate actually enforce:400 INVALID_SORTaccount.name)400 INVALID_SORTSEARCH_VIRTUAL_TYPES=formulatoday)sortable: false, reason: 'virtual-type'externalobject (#7865/#10474)sortable: true, caveat: 'unprovisioned-anchor'Considered and deliberately not members (rationale in the module docblock):
summary/autonumber(sort correctly — measured #6924; virtuality is the storage predicate, never the write contractCOMPUTED_VALUE_TYPES);encrypted/secret/json/vectoretc. (stored columns, no door refuses them — marking them unsortable would invent an unenforced refusal, the mirror image of declared≠enforced);created_at/updated_aton asystemFields: falseobject (gate hard-admits them, no column provisioned — absent from the served field map, hence absent from the signal: the projection may be narrower than a gate that is known to degrade).Deliverable 2 — declare + serve the projection
Contract (
packages/spec/src/api/sortability.zod.ts, new):FieldSortabilitySchema/ObjectSortabilitySchema+ the pure resolverresolveObjectSortability(doc). Derived from the spec's own predicates —isVirtualSearchField/SEARCH_VIRTUAL_TYPES(data/search-fields.ts, the same storage fact the two doors andvalidate-sortable-fieldskey on) andunprovisionedInjectedColumns(data/injected-system-column-provenance.ts, the #7865 derivation) — so there is no second hand-maintained flag anywhere.GetMetaItemResponseSchemagains an optional envelope-levelsortabilitykey.Not an authorable key, by construction:
FieldSchemaisstrictObject, so a key inside the served document is rejected by name and would otherwise become authorable — the exact rejected shaperesolveInjectedColumnProvenance's docblock records. The projection rides the envelope besideitem; the document is byte-untouched.Serving door (mapped, as the dispatch required):
GET /api/v1/meta/:type/:nameinpackages/rest/src/rest-server.ts. The splice is intranslateMetaEnvelope— the one seam every single-item exit rebuilds its body through (#5563: cached, uncached, compound-name) — so the cached (default) branch serves the signal too, unlike the ADR-0010 lock keys (which come from the lock resolver the cached path never consults). Computed from the final post-ADR-0106-masking document, so the projection's domain equals the field set the caller is served. The view read (/meta/view/:name) deliberately carries nothing new: a grid resolves each column against its bound object's projection (objectui's grid already reads object schema through exactly this route —data-objectstack.getObjectSchema→GET /meta/object/:name), and one projection per object cannot drift across the N views over it. The/meta/:typeLIST read is also unchanged: list items are bare documents (no envelope), and the strict-schema argument above applies to them verbatim.Oracle (the #9313/#10234 harness cells)
The 3 measured refusal cells are pinned against the shipped corpus, in the apps that ship them, with the displayed-column premise asserted alongside so the pins cannot go vacuously green:
examples/app-crm/test/sortability-projection-oracle.test.ts—crm_opportunity.expected_revenue(displayed in the aggregate's default list andlistViews.all) →sortable: false, reason: 'virtual-type'; anti-vacuity:amount/close_date/namestaysortable: true.examples/app-showcase/test/sortability-projection-oracle.test.ts—showcase_project.budget_remaining→ unsortable;budget/spentstay sortable.Coupling proof (projection tracks the predicate): the serving layer calls the spec resolver (structural identity, not agreement-by-coincidence); the rest test additionally asserts wire-projection ≡
resolveObjectSortability(served item)on both branches; and the spec test loops the unsortable pin overSEARCH_VIRTUAL_TYPESitself, so widening the predicate widens the projection with no edit to the pin.Tests
packages/spec/src/api/sortability.test.ts— resolver pins: predicate-coupled loop,summary/autonumberanti-overreach, anchor caveat (external vs author-declared vs local),idappend, both field-map shapes, wire validity.packages/rest/src/meta-object-sortability-projection.test.ts— envelope carries the projection on the cached (default) and uncached branches; plural-URL fold; a non-object read carries nosortabilitykey; agreement with the spec resolver.File surface
packages/spec/src/api/{sortability.zod.ts,sortability.test.ts,index.ts,protocol.zod.ts},packages/spec/src/type-alias-convention.pin.test.ts(the two new aliases join the ADR-0122 isomorphic pin registry — no defaults/transforms by design),packages/rest/src/{rest-server.ts,meta-object-sortability-projection.test.ts}, the two example oracle tests, one changeset, one hand-written docs count line (content/docs/getting-started/quick-reference.mdx, 28→29 — the gate-named consequence of the new reference page), and the regenerated spec artifacts (api-surface/,export-origins/,authorable-surface/,json-schema.manifest/,content/docs/references/**, strictness-ledger counts). Fenced files (packages/spec/src/data/field.zod.ts,packages/spec/src/data/object.zod.ts) untouched;packages/spec/src/ui/view.zod.tsuntouched.Verification
Per-gate verdict lines land in the
os-dev-reportcomment on #10235 (gate list derived bynode scripts/pm/dispatch-gates.mjsat the final head; CI convergence review is the PM's, per the standing dispatch contract).Generated by Claude Code
Generated by Claude Code