You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
publicPicker.sort is read by the public lookup route but stays undeclared after #7467 — one key of the enforced-never-declarable gap remains #7485
Found while implementing #7467 (declaring publicPicker on FormFieldSchema, per the maintainer ruling 「宣告」). Filed, not fixed: the ruling and the card bound that change to the card's four-key enumeration (displayFields, maxResults, filter, object), so a fifth key needs its own ruling rather than a rider.
The gap
GET /forms/:slug/lookup/:field in packages/rest/src/rest-server.ts performs a FIFTH read the #7467 card's enumeration did not include: the findData call's sort is
FormFieldPublicPickerSchema (landed by #7467) deliberately does not declare sort — the block is strict, so a form authoring publicPicker.sort is a loud unrecognized_keys parse error. That leaves sort in exactly the state the parent card was filed about, scoped to one key: enforced by the route, declarable nowhere. Authors get the fallback (first display field, ascending) and cannot change it.
This is pinned as intentional in packages/spec/src/ui/view-public-picker.test.ts (picker.sort is NOT declarable — undeclared reads stay loud until ruled on), so the state is loud, measured, and revisitable — but it still needs a disposition.
Decision needed
Same fork as the parent card, one key wide:
Declare it. Add sort to FormFieldPublicPickerSchema, deriving the shape from the route's actual read (an array passed verbatim to findData's query.sort; the route's own fallback is [{ field, order: 'asc' }], so the narrow declaration is Array<{ field: string, order: 'asc' | 'desc' }>). Flip the pin test.
Retire the read. Drop picker.sort ?? from the route so the fallback is the only behavior, closing the mirror-gap by removal (spec-property-retirement playbook, route side).
Given the parent ruling chose declaration for the block itself, option 1 is the consistent direction — but it changes the public acceptance surface, so it is a maintainer call, not dispatchable.
inside the same publicPicker branch whose four other keys the parent card enumerates (picker.object :7964, picker.displayFields :7985, picker.maxResults :7989, picker.filter :8000). So the fifth read is real and the card's arithmetic holds.
⚠️One stated premise is not on main yet, and it matters for sequencing. The card says FormFieldPublicPickerSchema "landed by #7467" deliberately omits sort, and cites a pin test packages/spec/src/ui/view-public-picker.test.ts. Neither exists on origin/main — publicPicker / PublicPicker return zero hits under packages/spec/src, and there is no *picker* file under packages/spec/src/ui. #7467 is still pm:dispatched with PR #7487 open, so both live in that PR. This does not weaken the card (option 2 — retiring the route read — is decidable and landable independently), but option 1 cannot be executed before #7467 merges. No pm:blocked applied: the ruling is not blocked, only one of its two executions.
Routing. Option 1 adds a key to a strict block in packages/spec — that changes the accepted-metadata set, and the seat's standing instruction ③ reverse red line ("any card that changes accept/reject behaviour, however small, is domain:spec") puts it in the protocol lane, alongside its parent #7467 (domain:spec). Recorded fork: if the ruling picks option 2 (drop picker.sort ?? from the route), the landing moves wholly to packages/rest ⇒ re-route to domain:cli at that point. Routing the card to where its parent's block is adjudicated is the choice that keeps one owner for one contract block.
Dedup. Repo-scoped scan of open objectstack issues + the 8 open PRs for publicPicker / sort / picker / lookup: #7467 (parent, in flight), #7486 (sibling, route-side read defect, filed the same minute), PR #7487. Distinct scopes, no duplicate — cross-linked here rather than converged, since each has its own disposition.
Release board. Not a defect card — it is a contract-shape fork with a loud, pinned current state — so the binary criterion does not apply and no target:<major> is produced.
Maintainer ruling — 2026-08-11. From the four-lens decision review (platform long-term coherence / measured business pull / AI-agent error-resistance / startup scope discipline); the maintainer accepted the recommendation set in full.
Ruling: retire the read (Option 2) — drop picker.sort ?? from the public lookup route; the fallback (first display field, ascending) becomes the only behavior; update the spec pin test to pin "not declarable AND not read".
Four-lens basis (this reverses the earlier consistency-with-parent lean): the parent's four keys were feature-necessary; sort has zero measured pull. Declaring would add a permanently-maintained public key and one more knob for metadata authors — human or AI — to misuse. If real demand for custom public-picker sort appears, run the declare fork then; it stays cheap. State: needs-user-decision → pm:queue. Landing: packages/rest route + the packages/spec pin test — stays with the spec seat, which owns the pin.
Recorded by the triage seat Routine (#5474 pilot) on the maintainer's direct instruction — ruling record, not a claim.
CLAIM — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-7485-retire-sort-read. Dispatching a cloud dev session (model: Opus — the ruling enumerates the change precisely; mechanical execution with a clear pin target).
Executing the maintainer ruling recorded 02:55Z (four-lens review): Option 2 — retire the read. Drop picker.sort ?? from the public lookup route (the fallback — first display field, ascending — becomes the only behavior); update the spec pin test to pin "not declarable AND not read". Landing: packages/rest route + packages/spec pin test, per the ruling record.
Serial check: PR #7487 (parent #7467) merged 03:24Z — no in-flight writers on the lookup branch or view-public-picker.test.ts. Scope boundary: sibling finding #7486 (legacy-spellings target fallback) is NOT this card — do not touch the referenceTo/target/options.objectName fallback chain.
DEV CLAIM — dev seat, session session_01Wft6gbSGhtq1J9nmBxZfNg. Branch: claude/issue-7485-retire-sort-read. Dispatched by the spec-lane PM (#6017, session session_01PiRUoQkTSBBmpyXBY3cVn2).
Executing the maintainer ruling recorded 02:55Z: Option 2 — retire the read. Dropping picker.sort ?? from GET /forms/:slug/lookup/:field in packages/rest/src/rest-server.ts so the composed query always sorts by [{ field: displayFields[0], order: 'asc' }]; extending the packages/spec/src/ui/view-public-picker.test.ts pin to say not declarable AND not read; adding a stored-row pin in packages/rest proving a pre-schema row carrying sort is ignored (not an error).
Scope boundaries acknowledged: sibling finding #7486 (the legacy-spellings referenceTo/target/options.objectName fallback chain) is not touched; the four declared publicPicker keys landed by #7487 stay exactly as they are; no content/docs/releases/, no docs/adr/**.
OS-DEV-REPORT — dev seat, session session_01Wft6gbSGhtq1J9nmBxZfNg. CI green (25 success + 1 skipped, 0 failures), PR #7509 open as DRAFT, mergeable_state: clean. Auto-merge NOT enabled — the PM lands it.
{
"issue": 7485,
"outcome": "complete",
"branch": "claude/issue-7485-retire-sort-read",
"pr_number": 7509,
"premise_check": {
"verdict": "confirmed",
"route_read": "packages/rest/src/rest-server.ts:8015 on c546c89 read `sort: picker.sort ?? [{ field: displayFields[0], order: 'asc' }]` — the triage seat's line number and text were exact",
"sequencing_premise_now_resolved": "the triage comment flagged that FormFieldPublicPickerSchema and view-public-picker.test.ts were not yet on main; PR #7487 merged 03:24Z, both are present, and the pin test was edited in place rather than created",
"unreachability_confirmed": "the strict block refuses `publicPicker.sort` with unrecognized_keys, so no form authored since #7467 could reach the read — measured, not assumed (RV3 below)"
},
"changes": [
{"file": "packages/rest/src/rest-server.ts", "what": "the lookup route's findData query is now `sort: [{ field: displayFields[0], order: 'asc' }]`; the `picker.sort ??` read is removed, with a comment recording the ruling and the ignore-not-error contract"},
{"file": "packages/rest/src/public-form-lookup-picker.test.ts", "what": "route-side pins: fixed-default sort on the existing composition test; a new `#7485` describe with (a) the real saveMetaItem path REFUSING a form authoring publicPicker.sort, with the byte-identical form minus `sort` as the passing control, (b) a pre-schema stored row carrying `sort` IGNORED — 200, ordering unchanged, the stored value reaching findData nowhere, (c) the fixed sort tracking displayFields[0] including the no-displayFields default"},
{"file": "packages/spec/src/ui/view-public-picker.test.ts", "what": "the pin now reads 'NOT declarable AND not read', asserts the refusal is unrecognized_keys naming `sort` rather than merely a failed parse, and its comment plus the file docblock state the read is retired and point at the rest-side half"},
{"file": "content/docs/ui/forms.mdx", "what": "the lookup-endpoint section states the fixed ordering (first displayFields entry, ascending) and that it is not configurable — hand-written page, permitted by the dispatch"},
{"file": ".changeset/retire-public-picker-sort-read.md", "what": "new changeset"}
],
"gates": {
"rest_suite": "82 files / 1344 passed",
"spec_suite": "374 files / 9802 passed",
"targeted": "the three public-form rest files 35/35; view-public-picker.test.ts 12/12",
"typecheck": "@objectstack/rest and @objectstack/spec both clean (spec includes check:scripts-typecheck + check:test-typecheck)",
"build": "pnpm build 71/71 tasks",
"ci": "26 checks — 25 success, 1 skipped (Console Pin Gate), 0 failures; includes Spec property liveness, Check Changeset, ESLint, TypeScript Type Check, Test Core 3/3, Dogfood Regression Gate 3/3, Temporal Conformance"
},
"generated_artifacts": {
"moved": false,
"statement": "no generated artifact moves. Verified by running a full `pnpm build` AFTER the edits and confirming `git status` showed only the intended hand-edited files. The change is route code, tests and one hand-written docs page; no spec schema was touched, so `packages/spec/authorable-surface/ui.json` and the rest of the ADR-0087 baselines are byte-identical. No os-regen was required or run.",
"corroboration": "RV3 below temporarily DID declare the key and `authorable-surface/ui.json` immediately gained a line and the build failed the drift guard — which is the positive control proving the artifact would have moved had this card gone the declare route, and does not move on the retire route"
},
"reverse_verification": {
"predicted_before_running": true,
"results": [
{"mutation": "restore `sort: picker.sort ?? [...]` in the route", "predicted": "the new stored-row pin goes RED at the query.sort assertion; everything else stays green", "observed": "1 failed / 5 passed — exactly the stored-row pin", "match": true},
{"mutation": "delete the fixed-sort composition (`sort:` key) entirely", "predicted": "all three query-composition pins RED — happy path, stored row, displayFields[0] tracking", "observed": "3 failed / 3 passed — exactly those three", "match": true},
{"mutation": "declare `sort` in FormFieldPublicPickerSchema", "predicted": "the spec pin AND the rest save-refusal pin both RED", "observed": "both RED; additionally `pnpm build` failed the artifact-drift guard on authorable-surface/ui.json — the guard working as designed", "match": true}
],
"restoration": "all three mutations reverted; spec rebuilt; the drift-guard artifact restored via `git checkout --`; both suites re-run green after restore before the commit"
},
"changeset": {
"file": ".changeset/retire-public-picker-sort-read.md",
"bump": "@objectstack/rest patch",
"justification": "Concurs with the dispatch's lean. PATCH, not minor or major: (a) no declared surface changes — `packages/spec` is touched only in a test file, the schema is byte-identical, so the accepted-metadata set is unchanged and `@objectstack/spec` takes no bump at all; (b) not a breaking change — the removed read was UNREACHABLE through the authoring path, since the strict block has rejected `publicPicker.sort` with unrecognized_keys since #7467, so no form that parses today changes behavior; (c) the only inputs that could change behavior are pre-#7467 stored rows that never went through ViewMetadataSchema, and for those the ruling's own disposition is that the key is dead data — which this PR pins as ignored-not-error rather than leaving to chance. A minor would claim a new capability (there is none) and a major would claim a break in observable behavior for a supported authoring input (there is none). The behavior of an option unreachable-until-yesterday narrowing to its own default is precisely a patch."
},
"deviations": [
{
"what": "The rest-side save-refusal pin asserts the refusal via a passing control save rather than by matching the error text for `sort`.",
"why": "Written first as `expect(detail).toContain('sort')`, which FAILED — through ViewMetadataSchema's union the failing ViewItem branch loses to the container branch's diagnostic, so the reported message names `viewKind`/`config` and never mentions `sort`. Matching that text would have pinned an unrelated wart and gone red the day it is fixed. The byte-identical form minus `sort` saving successfully proves `sort` is the sole cause without depending on message wording. Adjudicated within the ruling; filed as #7510 rather than fixed as a rider."
},
{
"what": "The docs edit ADDS a statement about ordering rather than correcting one.",
"why": "The dispatch said to make the lookup section state the fixed ordering IF it mentioned sort behavior. It did not mention ordering at all. Since the ordering is now a fixed contract with no authoring escape hatch, leaving it undocumented would make the only way to discover it reading the route, so the sentence was added. Two lines, inside the section #7487 created."
},
{
"what": "The worktree was created with `git worktree add --force` (the branch was already checked out in the shared primary), which left both trees sharing the branch ref; after committing, the primary reported a phantom staged inverse-diff.",
"why": "Resolved by detaching the primary's HEAD at c546c89 after verifying with an empty `git diff` (staged and unstaged) that its tree was byte-identical to origin/main and held no work. No file was modified and no commit was made to clear it. All edits, the commit and the push happened only in the dedicated worktree; `git stash` was never used."
}
],
"findings_filed": [
{
"issue": 7510,
"title": "A bad key inside a form field's publicPicker reports the CONTAINER branch's diagnostic",
"summary": "Through ViewMetadataSchema's union, a ViewItem-branch parse failure surfaces the container branch's error, so an author who typos a publicPicker subkey is told — confidently and in detail — to restructure a `viewKind`/`config` container that was correct all along; the key they actually got wrong is never named. The direct FormViewSchema door reports correctly, so only the union door (the one saveMetaItem and Studio use) degrades. Filed unassigned, not fixed: the fix means teaching the union to pick a best-progress branch, which moves error text for every view-parse failure in the repo and needs its own reverse verification.",
"not_caused_by_this_card": true
}
],
"docs_adr_touched": {
"content_docs_releases": "untouched",
"docs_adr": "untouched",
"hand_written_docs": ["content/docs/ui/forms.mdx"],
"note": "the Docs Drift Check bot flagged 9 hand-written pages reachable via @objectstack/rest; checked — only content/docs/ui/forms.mdx and content/docs/references/ui/view.mdx mention publicPicker at all, and the latter is schema-derived and unchanged since no schema was touched. The one releases-page hit (v15.mdx) is a passing reference to the managed-anchor deny, still accurate and read-only regardless."
},
"scope_boundaries_observed": {
"7486_legacy_spellings_fallback": "untouched — the referenceTo/target/options.objectName chain is byte-identical",
"four_declared_publicPicker_keys": "untouched — FormFieldPublicPickerSchema is byte-identical; the only spec change is in a .test.ts file"
},
"open_questions": []
}
One thing worth the PM's eye when landing. The rest-side pin deliberately does not assert on error text, because the text is wrong — see deviation 1 and #7510. If a future change makes the union report the ViewItem branch's diagnostic, that pin stays green and #7510's own test becomes the place the improvement is measured; nothing here needs revisiting.
The three reverse-verification mutations all matched their written predictions exactly, and probe 3 (declare sort) doubling as a positive control for the artifact-drift guard is a nice touch: it proves the generated-artifact claim ("nothing moves on the retire route") rather than asserting it.
Deviation 2 (docs sentence added rather than corrected) accepted — a fixed contract with no authoring escape hatch that is discoverable only by reading the route is exactly what a docs line is for.
Deviation 3 (worktree add --force, primary HEAD detached to clear a phantom staged inverse-diff, verified byte-identical to origin/main, no stash) — accepted, and worth noting as banked seat knowledge.
Activity
claude commentedon Aug 11, 2026
Triage:
needs-user-decision+domain:spec, no release-board label.Premise check on
origin/main@afdc6ea. The route-side half is live and exact:packages/rest/src/rest-server.ts:8015readsinside the same
publicPickerbranch whose four other keys the parent card enumerates (picker.object:7964,picker.displayFields:7985,picker.maxResults:7989,picker.filter:8000). So the fifth read is real and the card's arithmetic holds.FormFieldPublicPickerSchema"landed by #7467" deliberately omitssort, and cites a pin testpackages/spec/src/ui/view-public-picker.test.ts. Neither exists onorigin/main—publicPicker/PublicPickerreturn zero hits underpackages/spec/src, and there is no*picker*file underpackages/spec/src/ui. #7467 is stillpm:dispatchedwith PR #7487 open, so both live in that PR. This does not weaken the card (option 2 — retiring the route read — is decidable and landable independently), but option 1 cannot be executed before #7467 merges. Nopm:blockedapplied: the ruling is not blocked, only one of its two executions.Routing. Option 1 adds a key to a strict block in
packages/spec— that changes the accepted-metadata set, and the seat's standing instruction ③ reverse red line ("any card that changes accept/reject behaviour, however small, isdomain:spec") puts it in the protocol lane, alongside its parent #7467 (domain:spec). Recorded fork: if the ruling picks option 2 (droppicker.sort ??from the route), the landing moves wholly topackages/rest⇒ re-route todomain:cliat that point. Routing the card to where its parent's block is adjudicated is the choice that keeps one owner for one contract block.Dedup. Repo-scoped scan of open objectstack issues + the 8 open PRs for
publicPicker/sort/picker/lookup: #7467 (parent, in flight), #7486 (sibling, route-side read defect, filed the same minute), PR #7487. Distinct scopes, no duplicate — cross-linked here rather than converged, since each has its own disposition.Release board. Not a defect card — it is a contract-shape fork with a loud, pinned current state — so the binary criterion does not apply and no
target:<major>is produced.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 11, 2026
Maintainer ruling — 2026-08-11. From the four-lens decision review (platform long-term coherence / measured business pull / AI-agent error-resistance / startup scope discipline); the maintainer accepted the recommendation set in full.
Ruling: retire the read (Option 2) — drop
picker.sort ??from the public lookup route; the fallback (first display field, ascending) becomes the only behavior; update the spec pin test to pin "not declarable AND not read".Four-lens basis (this reverses the earlier consistency-with-parent lean): the parent's four keys were feature-necessary;
sorthas zero measured pull. Declaring would add a permanently-maintained public key and one more knob for metadata authors — human or AI — to misuse. If real demand for custom public-picker sort appears, run the declare fork then; it stays cheap. State:needs-user-decision→pm:queue. Landing:packages/restroute + thepackages/specpin test — stays with the spec seat, which owns the pin.Recorded by the triage seat Routine (#5474 pilot) on the maintainer's direct instruction — ruling record, not a claim.
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
CLAIM — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-7485-retire-sort-read. Dispatching a cloud dev session (model: Opus — the ruling enumerates the change precisely; mechanical execution with a clear pin target).Executing the maintainer ruling recorded 02:55Z (four-lens review): Option 2 — retire the read. Drop
picker.sort ??from the public lookup route (the fallback — first display field, ascending — becomes the only behavior); update the spec pin test to pin "not declarable AND not read". Landing:packages/restroute +packages/specpin test, per the ruling record.Serial check: PR #7487 (parent #7467) merged 03:24Z — no in-flight writers on the lookup branch or
view-public-picker.test.ts. Scope boundary: sibling finding #7486 (legacy-spellings target fallback) is NOT this card — do not touch thereferenceTo/target/options.objectNamefallback chain.Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
DEV CLAIM — dev seat, session
session_01Wft6gbSGhtq1J9nmBxZfNg. Branch:claude/issue-7485-retire-sort-read. Dispatched by the spec-lane PM (#6017, sessionsession_01PiRUoQkTSBBmpyXBY3cVn2).Executing the maintainer ruling recorded 02:55Z: Option 2 — retire the read. Dropping
picker.sort ??fromGET /forms/:slug/lookup/:fieldinpackages/rest/src/rest-server.tsso the composed query always sorts by[{ field: displayFields[0], order: 'asc' }]; extending thepackages/spec/src/ui/view-public-picker.test.tspin to say not declarable AND not read; adding a stored-row pin inpackages/restproving a pre-schema row carryingsortis ignored (not an error).Scope boundaries acknowledged: sibling finding #7486 (the legacy-spellings
referenceTo/target/options.objectNamefallback chain) is not touched; the four declaredpublicPickerkeys landed by #7487 stay exactly as they are; nocontent/docs/releases/, nodocs/adr/**.Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
OS-DEV-REPORT — dev seat, session
session_01Wft6gbSGhtq1J9nmBxZfNg. CI green (25 success + 1 skipped, 0 failures), PR #7509 open as DRAFT,mergeable_state: clean. Auto-merge NOT enabled — the PM lands it.{ "issue": 7485, "outcome": "complete", "branch": "claude/issue-7485-retire-sort-read", "pr_number": 7509, "premise_check": { "verdict": "confirmed", "route_read": "packages/rest/src/rest-server.ts:8015 on c546c89 read `sort: picker.sort ?? [{ field: displayFields[0], order: 'asc' }]` — the triage seat's line number and text were exact", "sequencing_premise_now_resolved": "the triage comment flagged that FormFieldPublicPickerSchema and view-public-picker.test.ts were not yet on main; PR #7487 merged 03:24Z, both are present, and the pin test was edited in place rather than created", "unreachability_confirmed": "the strict block refuses `publicPicker.sort` with unrecognized_keys, so no form authored since #7467 could reach the read — measured, not assumed (RV3 below)" }, "changes": [ {"file": "packages/rest/src/rest-server.ts", "what": "the lookup route's findData query is now `sort: [{ field: displayFields[0], order: 'asc' }]`; the `picker.sort ??` read is removed, with a comment recording the ruling and the ignore-not-error contract"}, {"file": "packages/rest/src/public-form-lookup-picker.test.ts", "what": "route-side pins: fixed-default sort on the existing composition test; a new `#7485` describe with (a) the real saveMetaItem path REFUSING a form authoring publicPicker.sort, with the byte-identical form minus `sort` as the passing control, (b) a pre-schema stored row carrying `sort` IGNORED — 200, ordering unchanged, the stored value reaching findData nowhere, (c) the fixed sort tracking displayFields[0] including the no-displayFields default"}, {"file": "packages/spec/src/ui/view-public-picker.test.ts", "what": "the pin now reads 'NOT declarable AND not read', asserts the refusal is unrecognized_keys naming `sort` rather than merely a failed parse, and its comment plus the file docblock state the read is retired and point at the rest-side half"}, {"file": "content/docs/ui/forms.mdx", "what": "the lookup-endpoint section states the fixed ordering (first displayFields entry, ascending) and that it is not configurable — hand-written page, permitted by the dispatch"}, {"file": ".changeset/retire-public-picker-sort-read.md", "what": "new changeset"} ], "gates": { "rest_suite": "82 files / 1344 passed", "spec_suite": "374 files / 9802 passed", "targeted": "the three public-form rest files 35/35; view-public-picker.test.ts 12/12", "typecheck": "@objectstack/rest and @objectstack/spec both clean (spec includes check:scripts-typecheck + check:test-typecheck)", "build": "pnpm build 71/71 tasks", "ci": "26 checks — 25 success, 1 skipped (Console Pin Gate), 0 failures; includes Spec property liveness, Check Changeset, ESLint, TypeScript Type Check, Test Core 3/3, Dogfood Regression Gate 3/3, Temporal Conformance" }, "generated_artifacts": { "moved": false, "statement": "no generated artifact moves. Verified by running a full `pnpm build` AFTER the edits and confirming `git status` showed only the intended hand-edited files. The change is route code, tests and one hand-written docs page; no spec schema was touched, so `packages/spec/authorable-surface/ui.json` and the rest of the ADR-0087 baselines are byte-identical. No os-regen was required or run.", "corroboration": "RV3 below temporarily DID declare the key and `authorable-surface/ui.json` immediately gained a line and the build failed the drift guard — which is the positive control proving the artifact would have moved had this card gone the declare route, and does not move on the retire route" }, "reverse_verification": { "predicted_before_running": true, "results": [ {"mutation": "restore `sort: picker.sort ?? [...]` in the route", "predicted": "the new stored-row pin goes RED at the query.sort assertion; everything else stays green", "observed": "1 failed / 5 passed — exactly the stored-row pin", "match": true}, {"mutation": "delete the fixed-sort composition (`sort:` key) entirely", "predicted": "all three query-composition pins RED — happy path, stored row, displayFields[0] tracking", "observed": "3 failed / 3 passed — exactly those three", "match": true}, {"mutation": "declare `sort` in FormFieldPublicPickerSchema", "predicted": "the spec pin AND the rest save-refusal pin both RED", "observed": "both RED; additionally `pnpm build` failed the artifact-drift guard on authorable-surface/ui.json — the guard working as designed", "match": true} ], "restoration": "all three mutations reverted; spec rebuilt; the drift-guard artifact restored via `git checkout --`; both suites re-run green after restore before the commit" }, "changeset": { "file": ".changeset/retire-public-picker-sort-read.md", "bump": "@objectstack/rest patch", "justification": "Concurs with the dispatch's lean. PATCH, not minor or major: (a) no declared surface changes — `packages/spec` is touched only in a test file, the schema is byte-identical, so the accepted-metadata set is unchanged and `@objectstack/spec` takes no bump at all; (b) not a breaking change — the removed read was UNREACHABLE through the authoring path, since the strict block has rejected `publicPicker.sort` with unrecognized_keys since #7467, so no form that parses today changes behavior; (c) the only inputs that could change behavior are pre-#7467 stored rows that never went through ViewMetadataSchema, and for those the ruling's own disposition is that the key is dead data — which this PR pins as ignored-not-error rather than leaving to chance. A minor would claim a new capability (there is none) and a major would claim a break in observable behavior for a supported authoring input (there is none). The behavior of an option unreachable-until-yesterday narrowing to its own default is precisely a patch." }, "deviations": [ { "what": "The rest-side save-refusal pin asserts the refusal via a passing control save rather than by matching the error text for `sort`.", "why": "Written first as `expect(detail).toContain('sort')`, which FAILED — through ViewMetadataSchema's union the failing ViewItem branch loses to the container branch's diagnostic, so the reported message names `viewKind`/`config` and never mentions `sort`. Matching that text would have pinned an unrelated wart and gone red the day it is fixed. The byte-identical form minus `sort` saving successfully proves `sort` is the sole cause without depending on message wording. Adjudicated within the ruling; filed as #7510 rather than fixed as a rider." }, { "what": "The docs edit ADDS a statement about ordering rather than correcting one.", "why": "The dispatch said to make the lookup section state the fixed ordering IF it mentioned sort behavior. It did not mention ordering at all. Since the ordering is now a fixed contract with no authoring escape hatch, leaving it undocumented would make the only way to discover it reading the route, so the sentence was added. Two lines, inside the section #7487 created." }, { "what": "The worktree was created with `git worktree add --force` (the branch was already checked out in the shared primary), which left both trees sharing the branch ref; after committing, the primary reported a phantom staged inverse-diff.", "why": "Resolved by detaching the primary's HEAD at c546c89 after verifying with an empty `git diff` (staged and unstaged) that its tree was byte-identical to origin/main and held no work. No file was modified and no commit was made to clear it. All edits, the commit and the push happened only in the dedicated worktree; `git stash` was never used." } ], "findings_filed": [ { "issue": 7510, "title": "A bad key inside a form field's publicPicker reports the CONTAINER branch's diagnostic", "summary": "Through ViewMetadataSchema's union, a ViewItem-branch parse failure surfaces the container branch's error, so an author who typos a publicPicker subkey is told — confidently and in detail — to restructure a `viewKind`/`config` container that was correct all along; the key they actually got wrong is never named. The direct FormViewSchema door reports correctly, so only the union door (the one saveMetaItem and Studio use) degrades. Filed unassigned, not fixed: the fix means teaching the union to pick a best-progress branch, which moves error text for every view-parse failure in the repo and needs its own reverse verification.", "not_caused_by_this_card": true } ], "docs_adr_touched": { "content_docs_releases": "untouched", "docs_adr": "untouched", "hand_written_docs": ["content/docs/ui/forms.mdx"], "note": "the Docs Drift Check bot flagged 9 hand-written pages reachable via @objectstack/rest; checked — only content/docs/ui/forms.mdx and content/docs/references/ui/view.mdx mention publicPicker at all, and the latter is schema-derived and unchanged since no schema was touched. The one releases-page hit (v15.mdx) is a passing reference to the managed-anchor deny, still accurate and read-only regardless." }, "scope_boundaries_observed": { "7486_legacy_spellings_fallback": "untouched — the referenceTo/target/options.objectName chain is byte-identical", "four_declared_publicPicker_keys": "untouched — FormFieldPublicPickerSchema is byte-identical; the only spec change is in a .test.ts file" }, "open_questions": [] }One thing worth the PM's eye when landing. The rest-side pin deliberately does not assert on error text, because the text is wrong — see deviation 1 and #7510. If a future change makes the union report the ViewItem branch's diagnostic, that pin stays green and #7510's own test becomes the place the improvement is measured; nothing here needs revisiting.
Generated by Claude Code
os-zhuang commentedon Aug 11, 2026
Step-7 review — ACCEPT (spec-lane PM seat #6017,
session_01PiRUoQkTSBBmpyXBY3cVn2).c546c89, and the triage seat's sequencing caveat resolved correctly — feat(spec): declare publicPicker on FormFieldSchema (#7467) #7487 merged 03:24Z, so the pin was edited in place rather than created.sort) doubling as a positive control for the artifact-drift guard is a nice touch: it proves the generated-artifact claim ("nothing moves on the retire route") rather than asserting it.sortcontrol save is stronger than a message match. Filing A bad key inside a form field'spublicPickerreports the CONTAINER branch's diagnostic — the author is told to fixviewKind/config, not the key they typed #7510 instead of riding the fix is correct scope discipline.worktree add --force, primary HEAD detached to clear a phantom staged inverse-diff, verified byte-identical to origin/main, no stash) — accepted, and worth noting as banked seat knowledge.packages/spectouched only in a test file), the removed read was unreachable through the authoring path sincepublicPickeris enforced by the REST lookup route but declared nowhere inpackages/spec— no saved form can ever enable it #7467, and the pre-schema stored-row case is pinned ignored-not-error rather than left to chance.publicPickerreports the CONTAINER branch's diagnostic — the author is told to fixviewKind/config, not the key they typed #7510's own test is where the improvement is measured. Nothing to revisit here.CI re-verified by this seat (26 checks: 25 success, 1 path-skipped). PR #7509 undrafted and auto-merge enabled; close-verify follows the merge notice.
Generated by Claude Code
refactor(rest): retire the public lookup route's picker.sort read (ob…