Skip to content

refactor(rest): retire the public lookup route's picker.sort read (#7485) - #7509

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-7485-retire-sort-read
Aug 11, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/issue-7485-retire-sort-read

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Executes the maintainer's four-lens ruling recorded on #7485 (02:55Z): Option 2 — retire the read.

Closes #7485

The gap this closes

GET /forms/:slug/lookup/:field composed its findData query with

sort: picker.sort ?? [{ field: displayFields[0], order: 'asc' }],

— a fifth read of the publicPicker block whose declaration (#7467, landed by PR #7487) enumerated four keys: displayFields, maxResults, filter, object. sort was in neither the schema nor the enumeration, which left it in exactly the state ADR-0049 calls the mirror-gap, one key wide: enforced by the route, declarable nowhere. The block is strict (ADR-0089 D3a), so a form authoring publicPicker.sort is an unrecognized_keys parse error — no form written since #7467 has ever reached that read.

The ruling closes the gap by removal rather than declaration: sort has zero measured pull, and declaring it would mean permanently maintaining one more public knob on an unauthenticated search surface. Ordering is now fixed — first display field, ascending — and is the only behavior. If real demand appears, the declare fork re-runs then; it stays cheap.

Changes

File Change
packages/rest/src/rest-server.ts the lookup route's query is sort: [{ field: displayFields[0], order: 'asc' }] — the picker.sort ?? read is gone
packages/rest/src/public-form-lookup-picker.test.ts route-side pins (below)
packages/spec/src/ui/view-public-picker.test.ts the existing pin now says "not declarable AND not read", and asserts the refusal is unrecognized_keys on sort rather than merely a failed parse
content/docs/ui/forms.mdx the public-picker section states the fixed ordering explicitly
.changeset/retire-public-picker-sort-read.md @objectstack/rest patch

Both sides of "ignored, not an error" are pinned, per the card:

  • the schema refuses a new form authoring publicPicker.sort through the real saveMetaItem write path — no row is written (with the byte-identical form minus sort saving as the control, so sort is provably the sole cause);
  • a pre-publicPicker is enforced by the REST lookup route but declared nowhere in packages/spec — no saved form can ever enable it #7467 stored row still carrying sort — rows persisted before the block was declared never went through ViewMetadataSchema, so one can carry a key the schema refuses today — is read past, answered 200, ordering unchanged, and the stored { field: 'email', order: 'desc' } reaches findData nowhere;
  • the fixed sort tracks displayFields[0], including the no-displayFields default (it is "first display field, ascending", not a frozen constant).

That stored-row half is why the route pin lives in packages/rest rather than the spec suite: the route must neither honor the stale key nor choke on it — a 500 on an anonymous surface would be a regression the removal itself caused.

Impact

None for any form that parses today — the read was unreachable through the authoring path. The declared four publicPicker keys are untouched.

Gates

Gate Result
@objectstack/rest full suite 82 files / 1344 passed
@objectstack/spec full suite 374 files / 9802 passed
@objectstack/rest + @objectstack/spec typecheck clean
pnpm build 71/71 tasks, no generated artifact moved (git status clean but for the 5 intended files)

Reverse verification

Predicted in writing before running; all three landed exactly as predicted.

Mutation Predicted Observed
restore sort: picker.sort ?? … stored-row pin RED, rest green 1 failed / 5 passed — the stored-row pin
delete the sort: key entirely all three composition pins RED 3 failed / 3 passed — happy path, stored row, displayFields[0] tracking
declare sort in FormFieldPublicPickerSchema spec pin and rest save-refusal pin RED both RED (and pnpm build failed the artifact-drift guard on authorable-surface/ui.json — the guard working as designed)

Scope boundaries observed

Sibling finding #7486 (the target fallback reading only referenceTo / target / options.objectName) is not touched. No content/docs/releases/, no docs/adr/**.

Finding noted, not fixed here

Through ViewMetadataSchema's union, a form that fails the ViewItem branch on a bad publicPicker subkey reports the container branch's diagnostic instead — the author sees Unrecognized key(s) on this view container: viewKind, config rather than a message naming sort. Pre-existing, unrelated to this change (the direct FormViewSchema door names the key correctly), and recorded on the issue rather than fixed as a rider.


Generated by Claude Code

)

`GET /forms/:slug/lookup/:field` composed its findData query with
`sort: picker.sort ?? [{ field: displayFields[0], order: 'asc' }]` — a
fifth read of the `publicPicker` block whose declaration (#7467)
enumerated four keys. `sort` was in neither the schema nor the
enumeration, leaving it enforced by the route and declarable nowhere:
ADR-0049's mirror-gap, one key wide. The strict block (ADR-0089 D3a)
rejects a form authoring `publicPicker.sort`, so no form written since
#7467 has ever reached the read.

Per the maintainer's four-lens ruling (recorded on the card 02:55Z),
this closes the gap by REMOVAL rather than declaration: `sort` has zero
measured pull, and declaring it would permanently maintain one more
public knob on an unauthenticated search surface. Ordering is now fixed
— first display field, ascending — and is the only behavior.

Pinned on both sides:

- `packages/spec/src/ui/view-public-picker.test.ts` — the existing pin
  now says "not declarable AND not read", and asserts the refusal is
  `unrecognized_keys` on `sort` rather than merely a failed parse.
- `packages/rest/src/public-form-lookup-picker.test.ts` — the composed
  query's sort is the fixed default; a pre-#7467 stored row still
  carrying `sort` is IGNORED, not an error (read past, answered 200,
  ordering unchanged); and the fixed sort tracks `displayFields[0]`,
  including the no-displayFields default.

The stored-row half is why the route pin lives in `packages/rest`: rows
persisted before #7467 declared the block never went through
`ViewMetadataSchema`, so one can carry a `sort` the schema refuses
today. The route must neither honor it nor choke on it.

No generated artifact moves — route code, tests and one hand-written
docs page only. Sibling finding #7486 (the legacy-spellings target
fallback) is untouched.

Closes #7485

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wft6gbSGhtq1J9nmBxZfNg
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 4:16am

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests protocol:ui tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • content/docs/permissions/authentication.mdx (via @objectstack/rest)
  • content/docs/permissions/system-context.mdx (via packages/rest)
  • content/docs/plugins/index.mdx (via @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/rest)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest)

⛔ 3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • content/docs/releases/v17.mdx (via @objectstack/rest)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

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.

publicPicker.sort is read by the public lookup route but stays undeclared after #7467 — one key of the enforced-never-declarable gap remains

2 participants