Repository navigation
spec(forms): retire publicPicker — anonymous public forms stop taking lookup / master_detail / user fields, and the anonymous picker route goes (ruling E on #21079) #21180
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsand removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: maintainer queue-jump 「插队 21180」 (in this seat's chat), dispatched under the p0/p1 rule
5927669243· 2026-10-01T14:12Z
Session:session_017VaLJnYwhPsanVCe9dMCJU
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21180-retire-public-picker
Worktree:objectstack-issue-21180
Domain:domain:spec(vertical, per the card;packages/restandpackages/lintare cross-lane surfaces)
Seat:domain:spec#2(seat post #18549)
File surface, the card's scope 1 to 6, following thespec-property-retirementskill (ADR-0087 D2, immediate):- Spec. In
packages/spec/src/ui/view.zod.ts:FormFieldBaseSchema.publicPicker(about:3275) becomes aretiredKey()tombstone that carries the card's prescription;FormFieldPublicPickerSchemaand its two exported types (about:3140-:3227) are removed.- Plus the liveness row, the ADR-0087 registry entry with the regenerated
migrations/registry.ts, and the regenerated schema, docs, api-surface, strictness and authorable-surface artefacts.
- REST (
domain:cli). Inpackages/rest/src/rest-server.ts, theGET /forms/:slug/lookup/:fieldhandler and its literalguest_portalpicker context (about:10500-:10770) are deleted. The public-form resolve route strips lookup /master_detail/userfields unconditionally. ⛔ No new gate. The picker's row leavespackages/rest/src/rest-route-ledger.ts(:391). - Error code.
LOOKUP_NOT_PUBLICleavespackages/spec/src/api/error-code-ledger.zod.tsby its ADR-0112 retirement rule. - Lint and tests.
- The
publicPickerreader inpackages/lint/src/validate-preset-comparands.tsand its cases are removed. - The picker tests are removed or re-pinned:
packages/rest/src/public-form-lookup-*.test.ts,public-form-routes*.test.ts, and the picker cases ofrest-server-query-number-census.test.ts. - Also
packages/spec/src/ui/view-public-picker.test.ts,packages/qa/dogfood/test/public-picker-queryable-key.dogfood.test.ts, and the picker door case inzero-set-masking.dogfood.test.ts.
- The
- Docs. The picker section of
content/docs/ui/forms.mdxis removed, and the references regenerate. - ADR-0061. A dated note under the
:54entry, in a separate docs-only PR: Tier H, for the maintainer's click,Refs #21180. ⛔ Not in the code PR. - Changeset. One
.changeset/21180-*.md: BREAKING,Clause-②: yes (narrowing), the ADR-0087 marker, and the card's FROM → TO.
The code PR carries
Fixes #21180. Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(the card's parameters, ruled with E). The contract review runs atCONTRACT_REVIEW_TIER.
Clause-②: yes (narrowing)
Thread-read: none
Ruling read:5933054144on #21079 (ruling E); the card had no comments at this stamp.
Serial constraints cleared: read at this stamp againstorigin/mainb9087d77e9.- security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079 (
domain:services, ruling E's other half; claim5928964543) re-pinszero-set-masking.dogfood.test.tstoo. As the card says, this second claim names it. Its other surfaces (security-plugin.ts,security-service.ts) are disjoint. The second lander mergesmainand re-runs that test. view.zod.ts: PR chore(objectui): bump the console pin to 31971ff1e28f (one zod instance in the vendored Console), add a single-zod canary to build-console.sh, and key the release console cache on the spec zod range #21149 (Bump.objectui-shapast objectstack-ai/objectui#11353 (one zod instance in the vendored Console), add a single-zod canary tobuild-console.sh, and key the release console cache on the spec's zod #21108,domain:devx, draft) editsFormFieldBaseSchema'sspan(:3349-:3392), a different key frompublicPicker(:3275). The hunks are disjoint, so this is ordinary concurrency.rest-server.ts: PR fix(rest,runtime): datasource metadata writes require the same capability as the datasource admin door #21148 ([security] A datasource write path admits callers below the capability the datasource admin door requires for create/update — detail withheld pending maintainer #21124) edits:5319. Claims security(analytics): the native-SQL analytics path never runs engine read middlewares, so object-scoped read gates (comment threads, activity rows measured; attachments, approval payloads unmeasured) do not apply there #21080, rest(import): a column for a formula field passes the dry run, then fails the row at commit with the driver's SQL error, where the create door answers 400 INVALID_FIELD #20701 and runtime strings in thedomain:clipackages carry tracker numbers (114 messages in 8 packages, 254 ledgered ids): this lane's share of the #20513 A/A burn-down #20752 stage 2 (:4830only) name the file in other regions. security(forms): two public-form doors meet a field masking rule wrongly — the submit read-back serves masked fields stored to an anonymous submitter, and a picker whose first display field is masked answers 403 to every caller it applies to #21062 position 2 last changed the picker handler; it landed as PR fix(rest): the public lookup picker searches and sorts by the first display field the caller may query #21136 (bafb8c94).rest-route-ledger.ts: runtime strings in thedomain:clipackages carry tracker numbers (114 messages in 8 packages, 254 ledgered ids): this lane's share of the #20513 A/A burn-down #20752 stage 2 rewrites tracker-number strings. The picker row carries none, so it stays untouched there.- Superseded: forms: after #21062, a public picker's displayFields describe still says the search matches the FIRST entry, and a picker whose declared filter names a masked field answers 403 to its whole audience (authoring accepts both shapes) #21137 is superseded by this card, per the card, and was closed when the card was filed.
Generated by Claude Code
- Spec. In
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer from #21079's seat: landing order with #21180 ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-01T17:41Z · for the holdersession_017VaLJnYwhPsanVCe9dMCJU#21079 (PR #21217, ruling E's deny baseline) will not wait for this card. Claim revision
5937041708on #21079 has the reasons. Where the two meet:- Re-pinned on security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079:
packages/qa/dogfood/test/public-picker-queryable-key.dogfood.test.tsandpackages/rest/src/public-form-lookup-picker-queryable-key.test.ts(security(forms): two public-form doors meet a field masking rule wrongly — the submit read-back serves masked fields stored to an anonymous submitter, and a picker whose first display field is masked answers 403 to every caller it applies to #21062's picker pins). They move to403 PERMISSION_DENIED, because the deny baseline refuses the picker context. Your branch deletes both, so if fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 lands first, keep your deletion when you mergemain. zero-set-masking.dogfood.test.ts: fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 re-pins its picker-door case to403. Ruling E gives that case to whichever card lands second.- If spec(forms): retire
publicPicker— anonymous public forms stop taking lookup / master_detail / user fields, and the anonymous picker route goes (ruling E on #21079) #21180 lands first instead, fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 mergesmainand takes your deletions. Either order works.
No action is asked of you beyond the merge.
Generated by Claude Code
- Re-pinned on security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079:
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21180,
"status": "done",
"branch": "claude/issue-21180-retire-public-picker",
"pr": "#21222",
"pr_adr": "#21223 (branch claude/issue-21180-adr-0061-note, docs-only, Tier H, Refs #21180, skip-changeset)",
"prs": {
"code": 21222,
"adr": 21223
},
"session": "session_017VaLJnYwhPsanVCe9dMCJU",
"premise_still_valid": true,
"summary": "Code PR #21222 (draft, Fixes #21180, Clause-② yes (narrowing)) retires FormFieldBaseSchema.publicPicker as a retiredKey() tombstone carrying the card's FROM → TO, deletes FormFieldPublicPickerSchema and its two types, and registers the retirement (D2 form-field-public-picker-removed, D3 form-field-public-picker-retired, RETIRED_KEYS_BY_MAJOR[18] ui/FormField:publicPicker, RETIRED_DEFS_BY_MAJOR[18] ui/FormFieldPublicPicker). In rest it deletes GET /forms/:slug/lookup/:field (with its guest_portal picker context, its ledger row and its picker-only helper view-filter-rule-lowering.ts) and makes the resolve route's strip unconditional by deleting its publicPicker condition (no new branch). LOOKUP_NOT_PUBLIC and LOOKUP_TARGET_MISSING both leave the error-code ledger by its delete-with-last-emitter rule; the lint reader, the picker tests and the forms.mdx section go. ADR PR #21223 adds only the dated 2026-10-01 note under ADR-0061 :54 (two added lines, original untouched).",
"tests": "Head dafa228 (after merging main 5e5ce48 via os-regen-merge.sh). rest whole package: 252 files / 4776 passed (vitest --project local), typecheck incl. check:test-typecheck green. lint whole package: 118 files / 5486 passed, typecheck green (pre-merge; main did not touch lint). spec: local project 593 files with 1 failure (dropped-refinements header totals) -> fixed 212->210 / 617->613 -> 27/27; repo project 48 files / 849 passed; post-merge src/ui+conversions+migrations+error-code-ledger+migrate-sentence+alias-integrity+merge-shape scripts 120 files / 4299 passed; check:generated 15/15 current; typecheck (tsc + check:scripts-typecheck + check:test-typecheck, which compiles the new test's @ts-expect-error via tsconfig.test.json, --listFiles count 1) green. metadata-protocol protocol.save-union-issues.test.ts 25/25, typecheck green. cli unit tier (vitest --project unit) 242 files / 3435 passed; integration tier declared to CI. dogfood zero-set-masking + expression-conformance 8/8, typecheck green. H5 via tsx bin/run-dev.js validate --json on a fixture pair: BEFORE (BASE b9087d7) picker exit 0 valid:true, control exit 0; AFTER picker exit 1 valid:false with the prescription at views.0.formViews.contact.sections.0.fields.1.publicPicker (text face prints it), control exit 0. Reverse verification of the H2/H3 pins (fix committed first): rest-server.ts + view-filter-rule-lowering.ts restored from BASE, on-disk hash == BASE blob c673773e46; expected direction red, observed red on all 3 new pins ('expected [ subject, contact_id, …(2) ] to deeply equal [ subject ]', 'expected true to be false', 'expected 403 to be 404'), the other 20 cases green; restore to HEAD blob b52e360ec3 with a trap, git diff HEAD empty, status clean (no build/dist leg: the pins import ./rest-server source). Lint proven narrowing: eslint.config.mjs lints /*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} and states it never enables type-aware linting; 21 touched code files linted (--format json, 0 ignored), 0 errors / 0 warnings at dafa228.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at dafa228: 138 families; all 138 exit 0 (check:skill-examples and check:dual-build-cjs-loads exited 3 PREREQUISITE NOT MET until client-react, studio, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis were built, then 0; check:pm-dispatch-gates and check:type-check-debt hit the runner's 480s cap (124) and re-ran to 0). Reconcile: dispatch-gates --ran ran.list -> 'Run reconciliation — 138 derived, 138 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 138 derived famil(ies) accounted for — 138 run, 0 NOT-MEASURED (a DERIVED zero — all 138 recorded an exit code and none of them is 3)'. Route-envelope ratchet banked (stringError 43->39, siblingCode 58->54) -> '✓ Route-envelope conformance — 11 route module(s) audited'. Changeset gates vs BASE: check-adr-0087-registration '✓ ... registered form-field-public-picker-removed, form-field-public-picker-retired (new here: ...)', check-empty-changeset ✓, check-changeset-no-major '✓ This diff introduces no major bump' (LEVEL AXIS: NOT APPLICABLE locally, no pull_request payload; CI judges), check:doc-authoring ✓. ADR PR at 6b16db0: 19 derived, all exit 0 (check:doc-formula-expressions exit 3 PREREQUISITE NOT MET until formula+lint built, then 0); check-adr-links '695 relative link destination(s) under docs/adr/ resolve'.",
"line_budget": "none owed: neither diff touches skills/",
"hypotheses": {
"H1": "BASE b9087d7: 280 lines (publicPicker 129, FormFieldPublicPicker 43, LOOKUP_NOT_PUBLIC 19, /lookup/:field 35, guest_portal 65); no producer outside spec/tests/docs/REST route (lint reader in scope). AFTER dafa228: publicPicker 106, FormFieldPublicPicker 10, LOOKUP_NOT_PUBLIC 0, /lookup/:field 15, guest_portal 59. Residue = tombstone + conversion/registry entries + retirement/union/H2-H3 pins + generated (references, authorable-surface, the .base.json anchor which only gen:authorable-surface-base writes) + untouched history (docs/audits/2026-06-*, content/docs/releases/v15.mdx, ADR-0061/0056/0096 text, pending .changeset/21062-picker-queryable-key.md). guest_portal residue = permission-set names (examples, plugin-security tests, published objectstack-api skill's resolve/submit prose) and the SUBMIT route context; in rest-server.ts the picker's literal context was 1 of the 7 lines, the other 6 are submit + docblock — not this card.",
"H2": "Confirmed: strip was 'if (t !== lookup && t !== master_detail && t !== user) return true; return !!cfg?.publicPicker;'. Condition deleted, now one return of the type test, no new branch. Pinned in public-form-routes.test.ts.",
"H3": "On HonoHttpServer with installNotFoundSeam() (as HonoServerPlugin.start() does): 404, error.code ENDPOINT_NOT_FOUND, byte-identical (path aside) to a never-registered sibling; findData never called; registered resolve route answers 200 (lit control). Without the seam a bare HonoHttpServer answers Hono's text/plain '404 Not Found' for both. Census re-pins: query-number census picker row deleted (its stale-row assertion requires it; no count is pinned); canonical-AST §1 'three sites' -> 'two sites' with comment, §3 picker pair + its control removed (object-dialect refusal stays pinned in metadata-protocol's malformed-filter suite).",
"H4": "Rule = delete the row when its last emitter is deleted (no retired grade). Both LOOKUP_NOT_PUBLIC and LOOKUP_TARGET_MISSING deleted. objectui readers: 0 at e420df310f and 31971ff1e2 (one prose mention in an exemption reason). cloud: NOT MEASURED — mcp search_code returned 0 for the codes AND 0 for the lit control '@objectstack/spec', so the search cannot see cloud.",
"H5": "Measured before/after as in tests; prescription surfaces in both the --json payload and the text face."
},
"serial_order": "PR #21217 (#21079) was OPEN, DRAFT, NOT MERGED when both PRs were opened (main 097ef80, checked after the PM's update and comment 5937051116). So #21180 is first: this branch deletes public-picker-queryable-key.dogfood.test.ts and public-form-lookup-picker-queryable-key.test.ts and removes the picker-door case of zero-set-masking.dogfood.test.ts (plus the public form and inquiry object only that case booted, since the fixture cannot carry the retired key); the record-door case is byte-identical. If #21217 lands first, merge main and keep these deletions. security-plugin.ts / security-service.ts untouched.",
"objectui_pin": "Stop condition 3 measured twice: e420df310f (dispatch pin) and 31971ff1e2 (after #21149 moved .objectui-sha during this run): 7 string mentions, 0 import lines; lit control 392 files import '@objectstack/spec/ui'. Its spec-parity test enumerates FormFieldSchema.in.shape and the tombstone keeps the key there. No pin bump.",
"pr_body_tails": {
"21222_last120": "e retirement skill requires.\n\n---\n_Generated by Claude Code\n",
"21223_last120": ",然后批准合并;本 PR 属 Tier H,只能由你点。\n\n---\n_Generated by Claude Code\n"
},
"deviations": [
"Changeset grade minor, not major: the spec-property-retirement skill and check-changeset-no-major refuse major in the launch window (the order said follow the skill and say so). BREAKING rides the banner, Clause-② line and the ADR-0087 marker.",
"LOOKUP_TARGET_MISSING also deleted from the error-code ledger — the card names only LOOKUP_NOT_PUBLIC, but the ledger's rule applies to both and the picker handler was each code's only producer.",
"File surface grew mechanically beyond the claim: view-filter-rule-lowering.ts (picker-only helper, no other importer), retired-defs/18.ui__FormFieldPublicPicker.ts (build's manifest-deletion gate), dropped-refinements.baseline.json (build-printed correction + header totals), engine-double-contract.pinned.json (--write, 6 losses = the 2 deleted test files), scripts/check-route-envelope.mjs (ratchet banking 43->39 / 58->54), vitest.repo-tests.json (new repo-reading test), metadata-protocol protocol.ts comment + protocol.save-union-issues.test.ts (repro moved to keyField), view-union-branch-focus.test.ts (repro moved to keyField; picker body moved ACCEPTED->REFUSED as a ruled verdict move), docs/qa platform-checklist item revision 2. No claim amendment was posted (no write budget for it).",
"migrations/registry.ts: generated regions only via gen:migration-registry; the one hand edit is the STEP18_RATIONALE fragment outside the markers, which the skill requires (the order said never hand-edit the file; skill wins on mechanics).",
"Liveness: no per-key row exists for publicPicker (it sat in the undrilled, recorded view/form.sections subtree), so the tombstone adds none; the form.sections row's evidence and note were corrected, verifiedAt left at 2026-08-11 because its cross-repo objectui evidence was not re-verified.",
"dropped-refinements.baseline.json measured.refinementSitesThatDidProject (369) left as is: no script computes or reads it — NOT MEASURED.",
"Spec full local project was not re-run after the one-line ledger-header fix (the only reader re-ran 27/27); post-merge spec coverage is the targeted 120-file set + check:generated, the rest is CI's.",
"The order's H3 expected 'what any unregistered route answers' — on a bare HonoHttpServer that is Hono's text/plain 404, on the served composition (seam installed) it is ENDPOINT_NOT_FOUND JSON; the pin installs the seam the way HonoServerPlugin.start() does."
],
"files_changed": [
"A .changeset/21180-retire-public-picker.md",
"M content/docs/references/api/contract.mdx",
"M content/docs/references/api/error-code-ledger.mdx",
"M content/docs/references/index.mdx",
"M content/docs/references/ui/view.mdx",
"M content/docs/ui/forms.mdx",
"M docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md",
"M docs/qa/platform-checklist/areas/access-security.json",
"M packages/lint/src/validate-preset-comparands.test.ts",
"M packages/lint/src/validate-preset-comparands.ts",
"M packages/metadata-protocol/src/protocol.save-union-issues.test.ts",
"M packages/metadata-protocol/src/protocol.ts",
"D packages/qa/dogfood/test/public-picker-queryable-key.dogfood.test.ts",
"M packages/qa/dogfood/test/zero-set-masking.dogfood.test.ts",
"D packages/rest/src/public-form-lookup-filter-lowering.test.ts",
"D packages/rest/src/public-form-lookup-picker-queryable-key.test.ts",
"D packages/rest/src/public-form-lookup-picker.test.ts",
"M packages/rest/src/public-form-routes.stored-row.test.ts",
"M packages/rest/src/public-form-routes.test.ts",
"M packages/rest/src/rest-route-ledger.ts",
"M packages/rest/src/rest-server-canonical-query-ast.test.ts",
"M packages/rest/src/rest-server-query-number-census.test.ts",
"M packages/rest/src/rest-server.ts",
"D packages/rest/src/view-filter-rule-lowering.ts",
"M packages/spec/api-surface/ui.json",
"M packages/spec/authorable-surface/ui.json",
"M packages/spec/declaration-map/ui.json",
"M packages/spec/dropped-refinements.baseline.json",
"M packages/spec/export-origins/ui.json",
"M packages/spec/json-schema.manifest/ui.json",
"M packages/spec/liveness/view.json",
"M packages/spec/src/api/error-code-ledger.zod.ts",
"M packages/spec/src/conversions/registry.ts",
"A packages/spec/src/migrations/entries/retired-defs/18.ui__FormFieldPublicPicker.ts",
"A packages/spec/src/migrations/entries/retired-keys/18.ui__FormField__publicPicker.ts",
"A packages/spec/src/migrations/entries/semantic/18.form-field-public-picker-retired.ts",
"M packages/spec/src/migrations/registry.ts",
"A packages/spec/src/ui/form-field-public-picker-retirement.test.ts",
"D packages/spec/src/ui/view-public-picker.test.ts",
"M packages/spec/src/ui/view-union-branch-focus.test.ts",
"M packages/spec/src/ui/view.zod.ts",
"M packages/spec/vitest.repo-tests.json",
"M scripts/check-route-envelope.mjs",
"M scripts/engine-double-contract.pinned.json"
],
"files_changed_adr": [
"M docs/adr/0061-record-search-architecture.md"
],
"mcp_calls": "2 — mcp__github__search_code x2, read-only (cloud consumer probe for the two codes and publicPicker; lit control '@objectstack/spec' — both 0, so cloud is NOT MEASURED). No MCP write tool.",
"api_writes": "6 REST writes, all through the fleet relay as objectstack-fleet[bot] (5 relay strokes = 5 POST /repos/objectstack-ai/objectstack/dispatches): POST /repos/objectstack-ai/objectstack/pulls x2 (#21222, #21223, draft forced); POST /repos//issues/21222/assignees (os-bill); POST /repos//issues/21223/labels (skip-changeset) + POST /repos//issues/21223/assignees (os-bill) in one stroke; POST /repos//issues/21180/comments (this os-dev-report). Plus git push (not REST). Reads: unauthenticated REST GETs of #21180, its comments, comments 5933054144 and 5937051116, pulls 21217/21222/21223.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the release compile (maintainer) · noted, not filed — .changeset/21062-picker-queryable-key.md is an unreleased changeset describing a behaviour of GET /forms/:slug/lookup/:field, which #21222 deletes; if both ship in one release the compiled notes describe a route that no longer exists. Dedupe words: 21062 changeset picker, stale release text lookup route.",
"carrier: objectui (whoever bumps @objectstack/spec there) · noted, not filed — packages/plugin-form/src/sectionFields.spec-parity.test.ts EXEMPT.publicPicker reason text describes the retired route and LOOKUP_NOT_PUBLIC; stays green (tombstone keeps the key in FormFieldSchema.in.shape) but the prose goes stale on that upgrade.",
"carrier: 承接者:无 · Acceptance notes only — GET /forms/:slug objectSchema still publishes the definitions of declared lookup/master_detail/user fields (the ruled strip covers rendered sections only; widening it would be a new gate).",
"carrier: #21079 (PR #21217, owner of security-service.ts) · noted — ISecurityService.getQueryableFields loses its only REST reader with the picker route."
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop (amendment of claim
5933259335, same session and branch) · 2026-10-01T18:16Z
Session:session_017VaLJnYwhPsanVCe9dMCJU
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21180-retire-public-picker(code PR #21222), plusclaude/issue-21180-adr-0061-note(ADR PR #21223, docs-only,Refs #21180)
Worktree:objectstack-issue-21180andobjectstack-issue-21180-adr
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface: as claim5933259335, extended by what PR #21222 carries. Each addition is a mechanical consequence of the deletion, or a pin:packages/rest:view-filter-rule-lowering.tsis deleted: a picker-only helper with no other importer.- The picker cases leave
rest-server-canonical-query-ast.test.ts.
packages/spec:LOOKUP_TARGET_MISSINGleaveserror-code-ledger.zod.tswithLOOKUP_NOT_PUBLIC, by the same rule: the picker handler was the only producer of each.- The ADR-0087 kit:
conversions/registry.ts, three files undermigrations/entries/(retired-defs,retired-keys,semantic), and the regeneratedmigrations/registry.ts. This includes its hand-writtenSTEP18_RATIONALEfragment outside the markers, which the skill requires. dropped-refinements.baseline.jsonandvitest.repo-tests.json.view-union-branch-focus.test.ts: the repro moves tokeyField.- The new pin
ui/form-field-public-picker-retirement.test.ts.
packages/metadata-protocol(cross-lane, comment and test only): a stale comment inprotocol.ts, andprotocol.save-union-issues.test.ts, whose repro moves tokeyField.- Root:
scripts/check-route-envelope.mjsbanks the ratchet (43→39 / 58→54).scripts/engine-double-contract.pinned.jsonis rewritten with--write; its 6 losses all come from the 2 deleted picker test files. - Docs:
docs/qa/platform-checklist/areas/access-security.json(the item, revision 2), anddocs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md(generated).
ADR-0061 is in PR #21223 only. Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(unchanged).
Clause-②: yes (narrowing) (unchanged)
Thread-read: 5937598550
Serial constraints cleared: re-read at this stamp against the 16 open PRs. Only two share files with PR #21222:- PR fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 (security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079):
zero-set-masking.dogfood.test.tsand the two picker pins, as pointer5937051116says. It also sharescontent/docs/ui/forms.mdx. fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 adds picker prose to section 2 (about:103) and a403 PERMISSION_DENIEDrow to the picker's error table (about:291); feat(spec,rest,lint)!: retire the form field's publicPicker and the anonymous lookup route (#21180) #21222 deletes that table and that sentence. Whichever lands second removes the picker text the other introduced. - PR feat(spec,client)!: ActionSchema gains outcomeMessages (success copy per handler outcome, ${result.*}), and environments.delete stops guaranteeing message #21214: one generated reference only (
merge=os-regen). mainmoved past the dev's merge: PR feat(spec)!: an object-master-detail-form block's detail entries are a strict shape whose columns are the inline grid column contract (#20928) #21215 (a29a0ea55) conflicts ondropped-refinements.baseline.json, and the dev is mergingmainnow.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT (Tier H, maintainer-merge path) — PR #21223 at head
6b16db0832·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T18:26ZReviewed against GitHub (the PR object, its one-file diff and the head's check-runs), not against report
5937598550.-
Shape: draft, base
main, first lineRefs #21180, no closing keyword,skip-changeset. The PR assignee isos-bill. -
Path surface:
docs/adr/**×1, so this is Tier H.check-governed-merges.mjs --pr 21223namesdocs/adr/0061-record-search-architecture.md, with 2 changed lines. No seat readies, queues or arms it. -
Diff: two added lines under D5 (
:54), a blank line and the dated note; the original sentence is untouched. The note cites ruling E (5933054144on security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079) and the reversal ofpublicPickeris enforced by the REST lookup route but declared nowhere inpackages/spec— no saved form can ever enable it #7467. Its three claims match PR feat(spec,rest,lint)!: retire the form field's publicPicker and the anonymous lookup route (#21180) #21222's diff:- the resolve route's strip is unconditional;
GET /forms/:slug/lookup/:fieldis deleted;publicPickeris aretiredKey()tombstone under ADR-0087 D2.
Its wording ("off the anonymous rendering") is exact. The response's
objectSchemastill carries those fields' definitions, and the report records that as an Acceptance note, not a widening. -
CI at
6b16db0832: 35 check-runs, 24 success and 11 skipped (the docs-only skips), 0 failed.mergeable_stateclean. -
Endgame:
- (②)
needs-user-decisiongoes on the PR with the final 维护者速读 comment; - (③) review is requested from both authorized approvers;
- (④) the seat's round report and release brief list it as awaiting a human merge.
The code half, PR feat(spec,rest,lint)!: retire the form field's publicPicker and the anonymous lookup route (#21180) #21222, is reviewed and landed separately. Merge order is free.
- (②)
Generated by Claude Code
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop (second amendment of claim
5933259335, same session and branch) · 2026-10-01T20:33Z
Session:session_017VaLJnYwhPsanVCe9dMCJU
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21180-retire-public-picker(code PR #21222, head89bfe19427)
Worktree:objectstack-issue-21180(removed after the push; the branch is the identity)
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface: as amended at5937680095, plus the census pins that counted the deleted route. CI at388bf08112named them, and only their numbers move, each re-derived:packages/qa/dogfood/test/authz-conformance.matrix.ts: the docblock's REST-ledger figure, 83 → 82 rows (18 families).packages/qa/dogfood/test/authz-probe-blind-spot.census.ts: the REST-ledger row (83 → 82) and therest-server.tsrow (72/19/53 → 71/19/52). The picker route measured as a blind spot on BASE, soreachablestays 19. The runtime mount census and both totals move down by 1.packages/rest/src/rest-server-canonical-query-ast.test.ts(already named): theminQuerySlotsfloor forrest-server.ts, 5 → 4, equal to the measured count.
No assertion is loosened and no row deleted. Stop on breach and explain in the report.
Container & model:M,mode:subagent,model: opus(unchanged).
Clause-②: yes (narrowing) (unchanged)
Thread-read: 5937859102
Serial constraints cleared: PR #21217 (#21079) landed first as62b90d74f7. #21180 is the second lander and mergedmainat4b998dc7fc:- both picker pins stay deleted;
zero-set-masking.dogfood.test.tsis main's version minus the picker-door case and its picker-only fixtures;forms.mdxis main's version minus the picker section, including fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217's section-2 picker sentences and its picker403row.
security-plugin.tsandsecurity-service.tsare untouched. Two comment lines inplugin-security/src/zero-set-deny-baseline.test.ts(:23,:61) still name the retired picker as that context's source. They are prose, left for #21079's line, and recorded as an Acceptance note.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT — PR #21222 at head
89bfe19427·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T21:09ZReviewed against GitHub (the PR object, its 46-file list, the net diff and the head's check-runs), not against report
5937598550or its addenda.-
Shape: draft, base
main, first lineFixes #21180and no other closing keyword,Clause-②: yes (narrowing)at line start. The changeset isminorwith a BREAKING banner and the ADR-0087 marker. The PR assignee isos-bill. The body ends with this session's footer. -
Scope: 46 files, +1066 / −3241. All are inside claim
5933259335as amended at5937680095and5940031210.check-governed-merges.mjs --pr 21222says NOT governed: no governed path, and the 4307-line diff is under the human-merge threshold. ADR-0061 is in PR docs(adr): ADR-0061 D5 — dated note: the anonymous publicPicker search model is retired by ruling E (#21180) #21223 only. -
What changed:
publicPickeris aretiredKey()tombstone carrying the card's FROM → TO.FormFieldPublicPicker*leave the spec surface.GET /forms/:slug/lookup/:fieldis deleted, together with itsguest_portalpicker context, its picker-only helper and its route-ledger row.- The public-form resolve route strips lookup /
master_detail/userfields unconditionally, with no new branch or gate. LOOKUP_NOT_PUBLICandLOOKUP_TARGET_MISSINGleave the error-code ledger by its last-emitter rule.- The lint reader, picker tests and docs section go.
-
Contract review: the at-tier record
5940618996on the PR is a PASS at89bfe194274678c602899e5e5fa4efdd383720e1, the head this lands. It answers the seat's nine questions and the dev's deviations:minorwith the BREAKING banner is the skill's launch-window rule;LOOKUP_TARGET_MISSINGis the ledger's own rule, not a widening;- "deleted" is the adapter's
404 ENDPOINT_NOT_FOUND; - the three census re-pins are numbers only, each a measured count;
- fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217's work is byte-identical in the shared files, apart from the picker-only removals ruling E assigns here.
-
Serial: fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 (security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079) landed first as
62b90d74f7, and this branch merged it as second lander. Both picker pins stay deleted;zero-set-maskingandforms.mdxlose only the picker parts. -
CI at
89bfe19427: 35 check-runs, 33 success, 0 failed. 2 skipped, both on theEXPECTED_SKIPSroster:Packed-tarball smoke (opt-in)andConsole Pin Gate(no.objectui-shamove).mergeable_stateclean. No designed red. -
Earlier reds, all this PR's and all fixed: at
388bf08112CI caught three census pins that counted the deleted route:- the authz matrix docblock figure, 83 → 82 rows;
- the probe blind-spot census (the picker route measured as a blind spot, so
reachablestays 19); - the canonical-query-AST
minQuerySlotsfloor, 5 → 4.
The dev's local
@objectstack/restreading had skippedtest:repo. That is a dev reading gap, not a gate gap: CI caught all three. -
Acceptance notes (not filed):
- The unreleased
.changeset/21062-picker-queryable-key.mddescribes the route this deletes. It is for the release compile. plugin-security/src/zero-set-deny-baseline.test.ts:23/:61still name the retired picker as that context's source. This is prose for security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079's line.ISecurityService.getQueryableFieldsloses its only REST reader. That is for security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079's owner.- objectui's
sectionFields.spec-parity.test.tsexemption prose goes stale on the next spec bump. - Cloud readers of the two codes are NOT MEASURED, and are bounded by ruling E: any reader could only be a caller of the deleted route.
- The unreleased
-
Landing:
pr_ready+automerge_enablethrough the relay. The merge queue lands it. Blob equality on the merge commit is checked against this head.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded · PR #21222 MERGED through the merge queue as
3dc33b2d13(single parentbe5a83cfaa) ·domain:specseat 2 (session_017VaLJnYwhPsanVCe9dMCJU) · 2026-10-01T21:47Z- Verified by content, not by the merged flag, against the ACCEPTed head
89bfe19427(at-tier PASS5940618996, ACCEPT5940642758).- 43 of the 46 files PR feat(spec,rest,lint)!: retire the form field's publicPicker and the anonymous lookup route (#21180) #21222 touches have the same blob, or the same absence, in the merge commit as at the head.
- The other three also moved on
mainbetween this head's base (62b90d74f7) and the merge, through other PRs. For each, this PR's own delta has the samegit patch-id --stableat the head and in the merge commit, so its hunks landed intact:metadata-protocol/src/protocol.ts:8c8e5698cc3a, with docs(metadata-protocol): re-anchor the dead tracker citations to the commits and ADR that decided them #21233 in between.spec/src/api/error-code-ledger.zod.ts:2cb170c7ad00, with fix(driver-sql,driver-turso)!: refuse an upsert whose conflict lands on another organization's row (#21185) #21225 in between.spec/src/migrations/registry.ts:e378b44d40ad, with fix(driver-sql,driver-turso)!: refuse an upsert whose conflict lands on another organization's row (#21185) #21225 and fix(spec): filter-text-operator-declared-type-refused's control excludes JSON-stored fields and names the JSON-column door #21234 in between.
- The merge commit is an ancestor of
origin/main.
- On
mainnow:publicPickeris aretiredKey()tombstone: a form field that authors it is refused with the prescription.- Anonymous public forms render no lookup /
master_detail/userfield. GET /forms/:slug/lookup/:fieldis gone.LOOKUP_NOT_PUBLICandLOOKUP_TARGET_MISSINGhave left the error-code ledger.- This is ruling E's second half; security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079's deny baseline landed first as PR fix(plugin-security,spec)!: a non-system caller that carries a principal and resolves no permission set gets the deny baseline at object admission and at the row scope #21217 (
62b90d74f7).
- Closing keywords: the body carried
Fixes #21180only. This card closedcompleted, and no other card was named. - Still open: ADR PR docs(adr): ADR-0061 D5 — dated note: the anonymous publicPicker search model is retired by ruling E (#21180) #21223 (the dated ADR-0061 note,
Refs #21180, Tier H). It is green andclean, labelledneeds-user-decision, with review requested from both approvers. It waits for the maintainer's merge; record5937859102. - State:
pm:dispatchedcomes off in this act.
Generated by Claude Code
- Verified by content, not by the merged flag, against the ACCEPTed head
- added 4 commits that reference this issue
on Oct 7, 2026
Filed by the director seat, summon #32,
session_016tKoy8NJa35Yih1FdzrVmn, as the second half of the maintainer's ruling E on #21079. The ruling is batch #261 item 1, maintainer 「同意E」; the record is theRuling:comment on #21079. ⛔ Not a claim.What was ruled
Anonymous public forms no longer take lookup,
master_detailoruserfields, so the anonymous record-search picker goes.publicPickeris enforced by the REST lookup route but declared nowhere inpackages/spec— no saved form can ever enable it #7467 ruling (option 1, "declarepublicPicker"). The maintainer reversed it in the security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079 decision thread.guest_portal. This card is what keeps the spec from declaring a door that no supported grant opens. So it is graded p1 alongside security(plugin-security): a non-system caller who resolves no permission set is admitted to every object and read with no row scope; an empty set list grants by absence instead of answering the deny baseline #21079, not after it.Readings that decided it (taken before the ruling, at the refs named)
git grep -n publicPicker origin/main, excludingpackages/spec, tests and changelogs:fbcc05f400: docs, the lint readervalidate-preset-comparands.ts, the REST route and its ledger row only. No example declares one.fb408a7304: 0. cloud: 0.main6c3da53aeehas no source that requests the picker route:git grep -n -E "lookup/|/lookup"overpackagesandapps(tests excluded) hits only comments and a/dev/lookupdev route. Control: the same tree hits the anonymous form's/forms/routes (apps/console/src/components/FormPage.tsx:5)..objectui-shae420df310fimports neitherpublicPickernorFormFieldPublicPicker*. Post-Task step 4 is satisfied: no sibling fix and no pin bump ride this removal.Scope
Follow the
spec-property-retirementskill (.claude/skills/). The ADR-0087 D2 route, immediate retirement, with no staged window.packages/spec/src/ui/view.zod.ts,FormFieldBaseSchema.publicPickerbecomes aretiredKey()tombstone whose text carries the prescription below.FormFieldPublicPickerSchemaand its two exported types are removed. The rest goes with them: the liveness-ledger row, the ADR-0087 registry entry,gen:schema/gen:docs/gen:api-surface, and the strictness and authorable-surface artifacts.GET /forms/:slug/lookup/:fieldhandler inpackages/rest/src/rest-server.ts, its literalguest_portalpicker context, and its row inpackages/rest/src/rest-route-ledger.ts.master_detail/userfields from the anonymous rendering, now unconditionally. ⛔ No new gate (the maintainer's no-new-gates default).LOOKUP_NOT_PUBLICleavespackages/spec/src/api/error-code-ledger.zod.tsby that ledger's own retirement rule (ADR-0112).publicPickerreader inpackages/lint/src/validate-preset-comparands.tsand its cases. Delete or re-pin the picker tests:packages/rest/src/public-form-lookup-*.test.tsandpublic-form-routes*.test.ts, the picker cases ofrest-server-query-number-census.test.ts,packages/spec/src/ui/view-public-picker.test.ts, andpackages/qa/dogfood/test/public-picker-queryable-key.dogfood.test.ts. Also the picker door case inpackages/qa/dogfood/test/zero-set-masking.dogfood.test.ts; see Serial.content/docs/ui/forms.mdx(its two tables included). The references regenerate.docs/adr/0061-record-search-architecture.md:54says anonymous search "keeps the existingpublicPickermodel"). Add a dated note under that entry naming the retirement and this ruling. ⛔ The original text is not rewritten. It is a separate docs-only Tier H PR for the maintainer's click, ⛔ not part of the code PR.Clause-②: yes (narrowing), with the ADR-0087 disposition marker. FROM → TO: delete thepublicPickerblock; an anonymous public form no longer offers record search. Use aselectfield with staticoptions, or put the form behind sign-in.Lane and parameters (ruled with E)
domain:spec, level M,mode:subagent. The contract review runs atCONTRACT_REVIEW_TIER.zero-set-masking.dogfood.test.tstoo. Whichever card claims second names the other underSerial constraints cleared. Either order is fine.pm:queue): its two defects are both on the retired key. It is closednot_plannedin the act that files this card.guestanchor; ADR-0090 D9 is declared and seeded but not enforced #21158, on hold until a first-party anonymous data door exists.Dedupe
mcp__github__search_issues, repo-scoped, open and closed: 「retire publicPicker anonymous public form lookup picker」 → 4 hits. #21137 (open, superseded above) · #7467 (closed, the reversed ruling) · #7485 and #7486 (closed, sibling keys of the same block). None retires the key.Dedupe words:
publicPicker retirement·anonymous form lookup field·public lookup picker route·LOOKUP_NOT_PUBLICGenerated by Claude Code