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
Position picker (position-ref widget) lists the everyone audience anchor, which the server refuses as a grant target; filter the audience anchors out of the grant picker #12200
Filed by the epic PM of objectstack-ai/objectstack#15194 (session_01Rerax7QTjKMPCUZxQUtPFR, marchtian) at the maintainer's request on 2026-10-12: route this finding to the domain:ui lane. Priority: p1.
In the browser, against PR-5's head, the position picker listed 16 positions including everyone. That is the audience anchor that ADR-0131 declares as position metadata beside guest.
The server refuses an audience anchor as a grant target. This is recorded in the seat's ACCEPT on #22939 (objectstack-ai/objectstack comment 6118326033, "Recorded finding").
So a user can pick a value that the save then refuses.
Claim: PM loop round 6 (as filed by the epic PM of objectstack-ai/objectstack#15194 at the maintainer's request: measure first, then filter the audience anchors out of the grant picker by the registry's own mark, not by hard-coded names)
Session: session_016djJF12Qt14ejKR5Vjt4sK
Account: os-steve
Branch: claude/issue-12200-position-picker-anchors
Worktree: objectui-issue-12200
Domain: domain:ui
Seat: domain:ui#1
File surface, on objectui main9646e2e:
The picker:packages/fields/src/widgets/RegistryRefField.tsx, the position-ref widget. Its options leave out the audience anchors.
The mark:@objectstack/spec exports AUDIENCE_ANCHOR_POSITIONS from @objectstack/spec/identity (['everyone', 'guest'], position.zod.ts). The server's own anchor gate reads it.
objectui already reads that constant for the same pair: audienceAnchorsOf() in packages/app-shell/src/views/metadata-admin/catalog-activation.ts. It imports the module lazily because that file is in the console's eager closure.
So this card has a declared mark to use, and no names are hard-coded. A server-side companion is owed only if the dev measures that the constant does not reach this widget.
A stored anchor: a value that already names an anchor keeps rendering. The widget's existing "a stored name the catalog does not list stays selectable as its own item" rule decides how. It is pinned, not hidden.
Co-located tests, and .changeset/12200-position-picker-anchors.md (@object-ui/fieldspatch).
⛔ No new prop, widget option or type member: the widget is the grant picker, and a grant names no anchor. If measurement finds a position-ref field on which an anchor IS a valid value, stop and report: that needs a declaration, not a filter.
(Stop on breach; explain in the report.)
Container & model: M, mode:subagent, model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: no
Responsibility: objectui's position-ref grant picker (@object-ui/fields RegistryRefField), which lists every position the registry holds, the everyone and guest audience anchors included | the server refuses an audience anchor as a grant target (objectstack-ai/objectstack#22939's ACCEPT, comment 6118326033), and the spec publishes the anchor list as AUDIENCE_ANCHOR_POSITIONS | every admin assigning a position from a user's record once objectstack-ai/objectstack#22939 and the pin bump objectstack-ai/objectstack#22967 land; measured listed in that PR's browser check (comment 6120504736)
Thread-read: none
Serial constraints cleared: read at 2026-10-12T08:48Z on objectui main9646e2e:
Duplicates: no remote branch or open PR touches this card's subject.
The widget's own PR: objectui#12175 (088e0f7, objectui#7611 PR-5) is the last change to RegistryRefField.tsx, and it has landed.
This seat's round-6 neighbours are objectui#12205 (plugin-form's write payload) and objectui#12206 (plugin-detail's inline date editor). They touch none of these files.
os-dev-report
{
"issue": 12200,
"status": "done",
"branch": "claude/issue-12200-position-picker-anchors",
"pr": "#12219",
"session": "session_016djJF12Qt14ejKR5Vjt4sK — dispatched as a subagent of the domain:ui#1 seat; the id is the parent's",
"premise_still_valid": true,
"summary": "position-ref (@object-ui/fields RegistryRefField) now leaves out exactly the spec's AUDIENCE_ANCHOR_POSITIONS (everyone, guest) from its options, through one helper (nonGrantableNames) keyed on registryType === 'position'. A stored anchor still shows by its label beside its name as its own option, unflagged because the registry lists it. An anchors-only registry gives the typed-name box. permission-ref is unchanged. H1 holds: the constant is exported from the root entry and from /identity at the 17.6.0 floor and at the installed 17.7.0. The registry carries no per-item mark, so the spec constant is the mark, no name is hard-coded and no server companion is owed. H2 was measured three ways and the PR takes a third route. The widget IS in the eager ui-components chunk. A static /identity import put ui-components over its ceiling by 6.1 KB (eager-closure exit 1). The lazy import kept the eager closure green but gave @object-ui/fields an opaque-subpath finding in check:spec-floors, which changeset:publish runs before publishing. A static import from the root @objectstack/spec, as @object-ui/auth imports BUILTIN_MEMBERSHIP_ROLES, moves the eager closure by -5 bytes and is judged clean. H3: objectstack main has no position-ref field. objectstack-ai/objectstack#22939's head has exactly one, sys_user_position.position, where the server refuses an anchor.",
"tests": "At HEAD aa16a9c. (1) One command, pnpm exec vitest run --maxWorkers=2 over six files: the new RegistryRefField.audienceAnchors-12200, RegistryRefField.registryName-7611, widget-aria-invalid-registry-e2e, widget-dom-leak-e2e, plugin-form objectFormRegistryNameWidget-7611 and components form-data-source-wiring. Result: 'Test Files 6 passed (6)', 'Tests 311 passed (311)', lock VERDICT command-exit 0. (2) pnpm --filter @object-ui/fields type-check: the script name echoed as 'tsc --noEmit && tsc -p tsconfig.test.json', exit 0; tsc --listFilesOnly on tsconfig.test.json includes the new test file. (3) Ablations on the committed fix. Each went through objectstack scripts/ablation-replace.mjs, which proved the mutation on disk (anchor 1 to 0, blob changed) and the restore (blob == HEAD 8035f9351027, git diff HEAD empty). Results: A1 filter off, 3 failed / 12 passed; A2 stored-anchor option off, 1 failed / 14 passed; A3 partial list [AUDIENCE_ANCHOR_POSITIONS[0]], 3 failed / 12 passed. No dist is involved: vitest resolves the widget from src. (4) Counterfactual builds, each mutated and restored with blob proof, read from apps/console/dist/eager-closure.json against base 9646e2e. Static /identity: +6953 gz bytes and 'ui-components 288.3 KB / 282.2 KB ceiling (OVER by 6.1 KB)', check:eager-closure exit 1. Lazy import (commit 8bd8a14): +174 bytes, eager-closure exit 0, spec-floors '@object-ui/fields [opaque-subpath] reaches @objectstack/spec/identity only from artifacts this gate cannot read symbol names in'. Final static root import: -5 bytes, 'ui-components 281.6 KB / 282.2 KB ceiling (headroom 0.7 KB)', spec-floors '308 (subpath, symbol) pair(s) judged' against 307 for the lazy build, with no @object-ui/fields finding. (5) A Node ESM import of the built packages/fields/dist/index.js loaded 160 exports, with 'import { AUDIENCE_ANCHOR_POSITIONS as nn } from "@objectstack/spec"' in it. (6) ESLint on the 2 changed sources with --no-inline-config --format json: 2 files, 0 errors, 2 no-explicit-any warnings (test casts). Population: packages/fields lints with 'eslint .' under the root eslint.config.js. Invariance: no parserOptions.project or projectService in that config, so linting is not type-aware and this diff cannot move the result for an untouched file.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (the fleet-write relay transport, HTTP 204), executed as objectstack-fleet[bot] against objectstack-ai/objectui. Stroke 1, pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12219/assignees [os-steve]; read back 8323 bytes sent, 8323 stored, identical. Stroke 2: POST /repos//issues/12200/comments, this report. Also 4 git pushes (the empty-branch probe and 3 commits), which are not REST. Labels: 0 writes. No write to objectstack-ai/objectstack content.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed (drift that has not happened; the values agree today). objectstack plugin-security delegated-admin-gate.ts keeps ANCHOR_POSITIONS as a literal new Set(['everyone', 'guest']). It does not import the spec's AUDIENCE_ANCHOR_POSITIONS, which runtime catalog-activation, objectql security-catalog-namespace and verify rls do import. · dedupe words: ANCHOR_POSITIONS delegated-admin-gate, AUDIENCE_ANCHOR_POSITIONS second copy, audience anchor literal set",
"carrier: 承接者:无 · noted, not filed (no wrong answer today). objectui scripts/check-spec-range-floors.mjs specSymbols does not parse dynamic import(), so a symbol taken by await import() is never judged against the floor. app-shell's lazy /identity imports (audienceAnchorsOf, clientValidation) pass only because FlowReferenceField.tsx imports that subpath statically. A package that reaches a subpath only dynamically gets opaque-subpath on the publish lane, measured on this card's lazy commit 8bd8a14. · dedupe words: spec-floors dynamic import, opaque-subpath, specSymbols import call",
"carrier: the domain:ui seat (a patch round that widens the surface, or a docs-only follow-up) · noted, not filed. content/docs/guide/fields.md and packages/fields/README.md describe position-ref as listing the position registry and do not mention the anchor exclusion. One sentence in each would cover it. They are outside the claim's file surface; see deviations. · dedupe words: position-ref docs anchors, registry-name pickers README"
],
"gates": [
{
"name": "① turbo run build --filter='@object-ui/fields^...' --concurrency=2",
"exit": 0,
"verdict": "Tasks: 10 successful, 10 total (10 cached, FULL TURBO); dist present in the worktree"
},
{
"name": "② pnpm --filter @object-ui/fields type-check",
"exit": 0,
"verdict": "script echoed 'tsc --noEmit && tsc -p tsconfig.test.json'"
},
{
"name": "② targeted vitest (6 files)",
"exit": 0,
"verdict": "Test Files 6 passed (6), Tests 311 passed (311)"
},
{
"name": "③ pnpm check:eager-closure",
"exit": 0,
"verdict": "Console eager closure is 3179.0 KB gzipped across 291 of 2474 chunks (budget: 3204.6 KB, headroom: 25.5 KB); ui-components 281.6 KB / 282.2 KB ceiling (headroom 0.7 KB)"
},
{
"name": "③ pnpm check:phantom-deps",
"exit": 0,
"verdict": "Every in-scope import is declared by the package that publishes it."
},
{
"name": "③ pnpm check:vi-mock-specifiers",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:vi-mock-inherit (derived)",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:vi-mock-override-shape (derived)",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:spec-symbols (derived: a new spec symbol import)",
"exit": 0,
"verdict": "spec symbol derivation ... 0 untriaged collisions"
},
{
"name": "pnpm check:spec-floors (derived: a new spec symbol import)",
"exit": 1,
"verdict": "15 findings, all no-artifact for packages this tree did not build (NOT MEASURED for those). @object-ui/fields was built and has no finding; 308 pairs judged"
},
{
"name": "pnpm check:esm-specifiers (derived)",
"exit": 0,
"verdict": "specifiers-only leg green; the load leg is NOT MEASURED here, but a Node ESM import of the fields dist loads"
},
{
"name": "④ node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"
},
{
"name": "④ pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"name": "④ pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"name": "④ pnpm check:control-bytes",
"exit": 0,
"verdict": "OK (scanned 8457 tracked text file(s))"
},
{
"name": "④ pnpm check:new-line-citations",
"exit": 0,
"verdict": "0 new citation(s), enforcement report-only"
},
{
"name": "eslint, 2 changed sources (derived)",
"exit": 0,
"verdict": "0 errors, 2 no-explicit-any warnings"
}
],
"line_budget": "n/a — no skills/** or line-ratcheted ledger touched. The diff is +186/-7 over 3 files (git diff --stat 9646e2e..aa16a9c).",
"deviations": [
"Route: Zone 2 H2 offered a static or a lazy import of @objectstack/spec/identity. Both were measured and both failed a gate (static: eager-closure exit 1; lazy: spec-floors opaque-subpath). The PR imports statically from the root @objectstack/spec, keeping Zone 3's Set-from-AUDIENCE_ANCHOR_POSITIONS shape and its one helper. The branch still carries the superseded lazy commit 8bd8a14; the squash removes it.",
"H1 is partly falsified: the server's grant refusal (plugin-security delegated-admin-gate.ts) does not import the constant; it keeps a literal Set. runtime catalog-activation does import it. Recorded as a finding, not filed.",
"Docs: AGENTS.md rule #2 (docs-driven) asks for README and guide updates, and the claim's file surface excludes both. I kept to the surface and named the one-sentence change for the seat, instead of choosing a side in silence.",
"Commit trailers: the commits end with the model-free pair (Claude-Session plus Co-authored-by: Claude), per objectui AGENTS.md. The harness reminder asked for a model-named Co-Authored-By; the AGENTS rule takes precedence.",
"My first attempt at the static-/identity counterfactual was a no-op that ablation-replace.mjs refused, because the replacement contained the anchor; nothing landed and the file was restored. I re-ran it with an anchor that drops, and that run is the one reported.",
"check:spec-floors exits 1 locally because of 15 no-artifact findings for packages this tree did not build. Those are NOT MEASURED, not red. The nightly and publish lanes build everything.",
"No browser re-check was run after the change: it needs objectstack-ai/objectstack#22939's server. The on-record check is objectstack-ai/objectstack#22967 comment 6120504736."
],
"files_changed": [
"packages/fields/src/widgets/RegistryRefField.tsx",
"packages/fields/src/widgets/tests/RegistryRefField.audienceAnchors-12200.test.tsx",
".changeset/12200-position-picker-anchors.md"
]
}
Claim amendment: the two docs that describe position-ref join the surface · domain:ui seat 1 · session_016djJF12Qt14ejKR5Vjt4sK · 2026-10-12T09:37Z
This amends claim 6120881363.
PR objectui#12219 (head aa16a9c) changes what position-ref offers: the spec's AUDIENCE_ANCHOR_POSITIONS (everyone, guest) are left out. Two documents still describe the widget as listing the whole registry, and the dev kept to the claim's surface and named them:
content/docs/guide/fields.md, the "Naming a registry item by name" table row for position-ref ("the position registry");
packages/fields/README.md, "Registry-name pickers" ("The widget lists that registry …").
Under the repo's docs-driven rule, both change in the same PR. The file surface gains those two files: one sentence each, saying that position-ref leaves out the audience anchors (the spec's AUDIENCE_ANCHOR_POSITIONS), because the server refuses an anchor as a grant target, and that a stored anchor still shows. ⛔ Nothing else in either file. Clause-②: no stands.
os-dev-report
{
"issue": 12200,
"round": 2,
"status": "done",
"branch": "claude/issue-12200-position-picker-anchors",
"pr": "#12219",
"head": "a9b00b0 (on top of aa16a9c; fast-forward push aa16a9c..a9b00b0, no rebase, no force)",
"session": "session_016djJF12Qt14ejKR5Vjt4sK — dispatched as a subagent of the domain:ui#1 seat; the id is the parent's",
"premise_still_valid": true,
"summary": "Docs-only patch round under the claim amendment (comment 6121641360). Each file gains exactly one sentence, inserted right after the existing sentence about a stored name the registry lacks. No other word in either file changed: the one existing line at each insertion point was split at the sentence boundary, and the lines after it are byte-identical.",
"sentences": {
"content/docs/guide/fields.md (paragraph under the 'Naming a registry item by name' table)": "position-ref leaves out the audience anchors, the spec's AUDIENCE_ANCHOR_POSITIONS (everyone, guest), because the server refuses an anchor as a grant target; a stored anchor still shows as its own option.",
"packages/fields/README.md ('Registry-name pickers')": "position-ref leaves out the audience anchors (the spec's AUDIENCE_ANCHOR_POSITIONS: everyone, guest), because the server refuses an anchor as a grant target; a stored anchor still shows as its own option."
},
"tests": "Docs only; no source changed, so no vitest or type-check run is owed for this delta. The gates below ran at a9b00b0, after the commit.",
"mcp_calls": "0",
"api_writes": "1 relay stroke, POST /repos/objectstack-ai/objectstack/dispatches (relay transport), carrying POST /repos//issues/12200/comments (this comment). Also 1 git push (not REST). No pr_ready, no auto-merge, no label write, no PR body edit, no write to objectstack content.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": [
{
"name": "pnpm check:doc-fences",
"exit": 0,
"verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript ..."
},
{
"name": "pnpm docs:check-links",
"exit": 0,
"verdict": "Links are valid across 17 scan roots. (ran locally: MEASURED)"
},
{
"name": "pnpm check:control-bytes",
"exit": 0,
"verdict": "OK (scanned 8457 tracked text file(s); skipped 85 binary)"
},
{
"name": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "0 new citation(s), enforcement report-only then exit 0"
},
{
"name": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"
},
{
"name": "pnpm check:doc-types (derived: a content/docs edit)",
"exit": 0,
"verdict": "Scanned 227 doc file(s) ... green"
}
],
"line_budget": "n/a — no skills/** or line-ratcheted ledger touched. This delta is +8/-2 over 2 files (git diff --stat aa16a9c..a9b00b0).",
"deviations": [
"The changeset is unchanged: its text still matches the behaviour, and a README sentence is not published source under check-changeset-presence's rule.",
"The PR body still cites aa16a9c as the tested head. The body is not edited in this round; the new head is a9b00b0, and the delta is docs only."
],
"files_changed": [
"content/docs/guide/fields.md",
"packages/fields/README.md"
]
}
Reviewed against GitHub and the diff, not the reports (os-dev-report6121612387 and round 2 6121684948). Claim 6120881363, amended in 6121641360.
Verdict
Shape:
Draft on main. The first line is Fixes #12200, and Clause-②: no sits at a line start.
Assignee os-steve. No model identifier.
Scope: 5 files: the widget, its new pin, the changeset, and the two docs the amendment added. check-governed-merges: NOT governed.
The fix follows the ask: the registry's own mark, no hard-coded names.
RegistryRefField.tsx builds one Set from the spec's AUDIENCE_ANCHOR_POSITIONS. One helper, nonGrantableNames(type), answers it for position and nothing for any other registry.
The option list leaves those names out.
A stored anchor still renders as its own option, by its label beside its name.
A registry that lists only anchors falls back to the typed-name box. permission-ref is unchanged.
H1: the registry carries no per-item mark, so the spec constant is the mark. It is exported from the package root and from /identity at the 17.6.0 floor and at the installed 17.7.0. No server companion is owed.
H2, measured three ways, and the route taken is the third:
A static /identity import put the eager ui-components chunk over its ceiling by 6.1 KB (check:eager-closure exit 1).
A lazy import kept the closure green, but made check:spec-floors report @object-ui/fields as an opaque subpath on the publish lane.
A static import from the root @objectstack/spec, the form @object-ui/auth already uses for BUILTIN_MEMBERSHIP_ROLES, moves the eager closure by −5 bytes and is judged clean. It leaves ui-components 0.7 KB under its ceiling.
The pins carry weight. Ablations on the committed fix:
filter off: 3 red;
stored-anchor option off: 1 red;
a partial list holding only the first anchor: 3 red.
The restores are blob-equal.
Docs (round 2, the amendment's two files): one sentence each, quoted from the diff. position-ref "leaves out the audience anchors … because the server refuses an anchor as a grant target; a stored anchor still shows as its own option". Each matches the code and the pins, and nothing else in either file moved.
Changeset:@object-ui/fieldspatch. No export, prop or type change.
CI: the red Test on aa16a9c is the concurrency cancel of a superseded head ("Canceling since a higher priority waiting request … exists" on all eight shards). It is no reading of this change. The seat enqueues once every check on a9b00b0 is success or skipped by design.
Out-of-scope findings (carrier: this record; none filed)
objectstack's plugin-securitydelegated-admin-gate.ts keeps the anchors as a literal Set(['everyone', 'guest']) and does not import the spec's AUDIENCE_ANCHOR_POSITIONS, which other objectstack readers do. The values agree today, so this is drift waiting to happen, not a wrong answer.
objectui's check-spec-range-floors.mjs does not parse a dynamic import(), so a symbol taken only lazily is never judged against the floor. This was measured on this card's superseded lazy commit 8bd8a14. Nothing is wrong on main today.
Landing: ready and auto-merge through the merge queue once a9b00b0 is green. The browser recheck rides objectstack-ai/objectstack#22939 and the next pin bump.
The merge: through the merge queue, on top of 6c437b0. The card closed as completed through Fixes #12200. Read in this act: nothing else closed with it.
Content check: all 5 files the PR changes are blob-identical at a491b64 to the accepted head a9b00b0f47, which ACCEPT 6121707635 covers.
On main now:
The position-ref grant picker leaves out the audience anchors (everyone, guest), read from the spec's AUDIENCE_ANCHOR_POSITIONS, not from a hard-coded list. The server refuses an anchor as a grant target.
A stored anchor still shows as its own option.
permission-ref and every other registry picker are unchanged.
content/docs/guide/fields.md and the fields README say so in one sentence each.
Activity
objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 6 (as filed by the epic PM of objectstack-ai/objectstack#15194 at the maintainer's request: measure first, then filter the audience anchors out of the grant picker by the registry's own mark, not by hard-coded names)
Session:
session_016djJF12Qt14ejKR5Vjt4sKAccount:
os-steveBranch:
claude/issue-12200-position-picker-anchorsWorktree:
objectui-issue-12200Domain:
domain:uiSeat:
domain:ui#1File surface, on objectui
main9646e2e:packages/fields/src/widgets/RegistryRefField.tsx, theposition-refwidget. Its options leave out the audience anchors.@objectstack/specexportsAUDIENCE_ANCHOR_POSITIONSfrom@objectstack/spec/identity(['everyone', 'guest'],position.zod.ts). The server's own anchor gate reads it.audienceAnchorsOf()inpackages/app-shell/src/views/metadata-admin/catalog-activation.ts. It imports the module lazily because that file is in the console's eager closure..changeset/12200-position-picker-anchors.md(@object-ui/fieldspatch).position-reffield on which an anchor IS a valid value, stop and report: that needs a declaration, not a filter.Container & model:
M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"Clause-②: no
Responsibility:
objectui's position-ref grant picker (@object-ui/fields RegistryRefField), which lists every position the registry holds, the everyone and guest audience anchors included | the server refuses an audience anchor as a grant target (objectstack-ai/objectstack#22939's ACCEPT, comment 6118326033), and the spec publishes the anchor list as AUDIENCE_ANCHOR_POSITIONS | every admin assigning a position from a user's record once objectstack-ai/objectstack#22939 and the pin bump objectstack-ai/objectstack#22967 land; measured listed in that PR's browser check (comment 6120504736)Thread-read: none
Serial constraints cleared: read at 2026-10-12T08:48Z on objectui
main9646e2e:088e0f7, objectui#7611 PR-5) is the last change toRegistryRefField.tsx, and it has landed.marchtian, held by [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) objectstack#15194) routed this card to the lane. This seat posts its coordination here.plugin-form's write payload) and objectui#12206 (plugin-detail's inline date editor). They touch none of these files.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 12200,
"status": "done",
"branch": "claude/issue-12200-position-picker-anchors",
"pr": "#12219",
"session": "session_016djJF12Qt14ejKR5Vjt4sK — dispatched as a subagent of the domain:ui#1 seat; the id is the parent's",
"premise_still_valid": true,
"summary": "position-ref (@object-ui/fields RegistryRefField) now leaves out exactly the spec's AUDIENCE_ANCHOR_POSITIONS (everyone, guest) from its options, through one helper (nonGrantableNames) keyed on registryType === 'position'. A stored anchor still shows by its label beside its name as its own option, unflagged because the registry lists it. An anchors-only registry gives the typed-name box. permission-ref is unchanged. H1 holds: the constant is exported from the root entry and from /identity at the 17.6.0 floor and at the installed 17.7.0. The registry carries no per-item mark, so the spec constant is the mark, no name is hard-coded and no server companion is owed. H2 was measured three ways and the PR takes a third route. The widget IS in the eager ui-components chunk. A static /identity import put ui-components over its ceiling by 6.1 KB (eager-closure exit 1). The lazy import kept the eager closure green but gave @object-ui/fields an opaque-subpath finding in check:spec-floors, which changeset:publish runs before publishing. A static import from the root @objectstack/spec, as @object-ui/auth imports BUILTIN_MEMBERSHIP_ROLES, moves the eager closure by -5 bytes and is judged clean. H3: objectstack main has no position-ref field. objectstack-ai/objectstack#22939's head has exactly one, sys_user_position.position, where the server refuses an anchor.",
"tests": "At HEAD aa16a9c. (1) One command, pnpm exec vitest run --maxWorkers=2 over six files: the new RegistryRefField.audienceAnchors-12200, RegistryRefField.registryName-7611, widget-aria-invalid-registry-e2e, widget-dom-leak-e2e, plugin-form objectFormRegistryNameWidget-7611 and components form-data-source-wiring. Result: 'Test Files 6 passed (6)', 'Tests 311 passed (311)', lock VERDICT command-exit 0. (2) pnpm --filter @object-ui/fields type-check: the script name echoed as 'tsc --noEmit && tsc -p tsconfig.test.json', exit 0; tsc --listFilesOnly on tsconfig.test.json includes the new test file. (3) Ablations on the committed fix. Each went through objectstack scripts/ablation-replace.mjs, which proved the mutation on disk (anchor 1 to 0, blob changed) and the restore (blob == HEAD 8035f9351027, git diff HEAD empty). Results: A1 filter off, 3 failed / 12 passed; A2 stored-anchor option off, 1 failed / 14 passed; A3 partial list [AUDIENCE_ANCHOR_POSITIONS[0]], 3 failed / 12 passed. No dist is involved: vitest resolves the widget from src. (4) Counterfactual builds, each mutated and restored with blob proof, read from apps/console/dist/eager-closure.json against base 9646e2e. Static /identity: +6953 gz bytes and 'ui-components 288.3 KB / 282.2 KB ceiling (OVER by 6.1 KB)', check:eager-closure exit 1. Lazy import (commit 8bd8a14): +174 bytes, eager-closure exit 0, spec-floors '@object-ui/fields [opaque-subpath] reaches @objectstack/spec/identity only from artifacts this gate cannot read symbol names in'. Final static root import: -5 bytes, 'ui-components 281.6 KB / 282.2 KB ceiling (headroom 0.7 KB)', spec-floors '308 (subpath, symbol) pair(s) judged' against 307 for the lazy build, with no @object-ui/fields finding. (5) A Node ESM import of the built packages/fields/dist/index.js loaded 160 exports, with 'import { AUDIENCE_ANCHOR_POSITIONS as nn } from "@objectstack/spec"' in it. (6) ESLint on the 2 changed sources with --no-inline-config --format json: 2 files, 0 errors, 2 no-explicit-any warnings (test casts). Population: packages/fields lints with 'eslint .' under the root eslint.config.js. Invariance: no parserOptions.project or projectService in that config, so linting is not type-aware and this diff cannot move the result for an untouched file.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (the fleet-write relay transport, HTTP 204), executed as objectstack-fleet[bot] against objectstack-ai/objectui. Stroke 1, pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12219/assignees [os-steve]; read back 8323 bytes sent, 8323 stored, identical. Stroke 2: POST /repos//issues/12200/comments, this report. Also 4 git pushes (the empty-branch probe and 3 commits), which are not REST. Labels: 0 writes. No write to objectstack-ai/objectstack content.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed (drift that has not happened; the values agree today). objectstack plugin-security delegated-admin-gate.ts keeps ANCHOR_POSITIONS as a literal new Set(['everyone', 'guest']). It does not import the spec's AUDIENCE_ANCHOR_POSITIONS, which runtime catalog-activation, objectql security-catalog-namespace and verify rls do import. · dedupe words: ANCHOR_POSITIONS delegated-admin-gate, AUDIENCE_ANCHOR_POSITIONS second copy, audience anchor literal set",
"carrier: 承接者:无 · noted, not filed (no wrong answer today). objectui scripts/check-spec-range-floors.mjs specSymbols does not parse dynamic import(), so a symbol taken by await import() is never judged against the floor. app-shell's lazy /identity imports (audienceAnchorsOf, clientValidation) pass only because FlowReferenceField.tsx imports that subpath statically. A package that reaches a subpath only dynamically gets opaque-subpath on the publish lane, measured on this card's lazy commit 8bd8a14. · dedupe words: spec-floors dynamic import, opaque-subpath, specSymbols import call",
"carrier: the domain:ui seat (a patch round that widens the surface, or a docs-only follow-up) · noted, not filed. content/docs/guide/fields.md and packages/fields/README.md describe position-ref as listing the position registry and do not mention the anchor exclusion. One sentence in each would cover it. They are outside the claim's file surface; see deviations. · dedupe words: position-ref docs anchors, registry-name pickers README"
],
"gates": [
{
"name": "① turbo run build --filter='@object-ui/fields^...' --concurrency=2",
"exit": 0,
"verdict": "Tasks: 10 successful, 10 total (10 cached, FULL TURBO); dist present in the worktree"
},
{
"name": "② pnpm --filter @object-ui/fields type-check",
"exit": 0,
"verdict": "script echoed 'tsc --noEmit && tsc -p tsconfig.test.json'"
},
{
"name": "② targeted vitest (6 files)",
"exit": 0,
"verdict": "Test Files 6 passed (6), Tests 311 passed (311)"
},
{
"name": "③ pnpm check:eager-closure",
"exit": 0,
"verdict": "Console eager closure is 3179.0 KB gzipped across 291 of 2474 chunks (budget: 3204.6 KB, headroom: 25.5 KB); ui-components 281.6 KB / 282.2 KB ceiling (headroom 0.7 KB)"
},
{
"name": "③ pnpm check:phantom-deps",
"exit": 0,
"verdict": "Every in-scope import is declared by the package that publishes it."
},
{
"name": "③ pnpm check:vi-mock-specifiers",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:vi-mock-inherit (derived)",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:vi-mock-override-shape (derived)",
"exit": 0,
"verdict": "OK"
},
{
"name": "pnpm check:spec-symbols (derived: a new spec symbol import)",
"exit": 0,
"verdict": "spec symbol derivation ... 0 untriaged collisions"
},
{
"name": "pnpm check:spec-floors (derived: a new spec symbol import)",
"exit": 1,
"verdict": "15 findings, all no-artifact for packages this tree did not build (NOT MEASURED for those). @object-ui/fields was built and has no finding; 308 pairs judged"
},
{
"name": "pnpm check:esm-specifiers (derived)",
"exit": 0,
"verdict": "specifiers-only leg green; the load leg is NOT MEASURED here, but a Node ESM import of the fields dist loads"
},
{
"name": "④ node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"
},
{
"name": "④ pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"name": "④ pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"name": "④ pnpm check:control-bytes",
"exit": 0,
"verdict": "OK (scanned 8457 tracked text file(s))"
},
{
"name": "④ pnpm check:new-line-citations",
"exit": 0,
"verdict": "0 new citation(s), enforcement report-only"
},
{
"name": "eslint, 2 changed sources (derived)",
"exit": 0,
"verdict": "0 errors, 2 no-explicit-any warnings"
}
],
"line_budget": "n/a — no skills/** or line-ratcheted ledger touched. The diff is +186/-7 over 3 files (git diff --stat 9646e2e..aa16a9c).",
"deviations": [
"Route: Zone 2 H2 offered a static or a lazy import of @objectstack/spec/identity. Both were measured and both failed a gate (static: eager-closure exit 1; lazy: spec-floors opaque-subpath). The PR imports statically from the root @objectstack/spec, keeping Zone 3's Set-from-AUDIENCE_ANCHOR_POSITIONS shape and its one helper. The branch still carries the superseded lazy commit 8bd8a14; the squash removes it.",
"H1 is partly falsified: the server's grant refusal (plugin-security delegated-admin-gate.ts) does not import the constant; it keeps a literal Set. runtime catalog-activation does import it. Recorded as a finding, not filed.",
"Docs: AGENTS.md rule #2 (docs-driven) asks for README and guide updates, and the claim's file surface excludes both. I kept to the surface and named the one-sentence change for the seat, instead of choosing a side in silence.",
"Commit trailers: the commits end with the model-free pair (Claude-Session plus Co-authored-by: Claude), per objectui AGENTS.md. The harness reminder asked for a model-named Co-Authored-By; the AGENTS rule takes precedence.",
"My first attempt at the static-/identity counterfactual was a no-op that ablation-replace.mjs refused, because the replacement contained the anchor; nothing landed and the file was restored. I re-ran it with an anchor that drops, and that run is the one reported.",
"check:spec-floors exits 1 locally because of 15 no-artifact findings for packages this tree did not build. Those are NOT MEASURED, not red. The nightly and publish lanes build everything.",
"No browser re-check was run after the change: it needs objectstack-ai/objectstack#22939's server. The on-record check is objectstack-ai/objectstack#22967 comment 6120504736."
],
"files_changed": [
"packages/fields/src/widgets/RegistryRefField.tsx",
"packages/fields/src/widgets/tests/RegistryRefField.audienceAnchors-12200.test.tsx",
".changeset/12200-position-picker-anchors.md"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Claim amendment: the two docs that describe
position-refjoin the surface ·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T09:37ZThis amends claim
6120881363.PR objectui#12219 (head
aa16a9c) changes whatposition-refoffers: the spec'sAUDIENCE_ANCHOR_POSITIONS(everyone,guest) are left out. Two documents still describe the widget as listing the whole registry, and the dev kept to the claim's surface and named them:content/docs/guide/fields.md, the "Naming a registry item by name" table row forposition-ref("thepositionregistry");packages/fields/README.md, "Registry-name pickers" ("The widget lists that registry …").Under the repo's docs-driven rule, both change in the same PR. The file surface gains those two files: one sentence each, saying that
position-refleaves out the audience anchors (the spec'sAUDIENCE_ANCHOR_POSITIONS), because the server refuses an anchor as a grant target, and that a stored anchor still shows. ⛔ Nothing else in either file.Clause-②: nostands.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 12200,
"round": 2,
"status": "done",
"branch": "claude/issue-12200-position-picker-anchors",
"pr": "#12219",
"head": "a9b00b0 (on top of aa16a9c; fast-forward push aa16a9c..a9b00b0, no rebase, no force)",
"session": "session_016djJF12Qt14ejKR5Vjt4sK — dispatched as a subagent of the domain:ui#1 seat; the id is the parent's",
"premise_still_valid": true,
"summary": "Docs-only patch round under the claim amendment (comment 6121641360). Each file gains exactly one sentence, inserted right after the existing sentence about a stored name the registry lacks. No other word in either file changed: the one existing line at each insertion point was split at the sentence boundary, and the lines after it are byte-identical.",
"sentences": {
"content/docs/guide/fields.md (paragraph under the 'Naming a registry item by name' table)": "
position-refleaves out the audience anchors, the spec'sAUDIENCE_ANCHOR_POSITIONS(everyone,guest), because the server refuses an anchor as a grant target; a stored anchor still shows as its own option.","packages/fields/README.md ('Registry-name pickers')": "
position-refleaves out the audience anchors (the spec'sAUDIENCE_ANCHOR_POSITIONS:everyone,guest), because the server refuses an anchor as a grant target; a stored anchor still shows as its own option."},
"tests": "Docs only; no source changed, so no vitest or type-check run is owed for this delta. The gates below ran at a9b00b0, after the commit.",
"mcp_calls": "0",
"api_writes": "1 relay stroke, POST /repos/objectstack-ai/objectstack/dispatches (relay transport), carrying POST /repos//issues/12200/comments (this comment). Also 1 git push (not REST). No pr_ready, no auto-merge, no label write, no PR body edit, no write to objectstack content.",
"open_questions": [],
"out_of_scope_findings": [],
"gates": [
{
"name": "pnpm check:doc-fences",
"exit": 0,
"verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript ..."
},
{
"name": "pnpm docs:check-links",
"exit": 0,
"verdict": "Links are valid across 17 scan roots. (ran locally: MEASURED)"
},
{
"name": "pnpm check:control-bytes",
"exit": 0,
"verdict": "OK (scanned 8457 tracked text file(s); skipped 85 binary)"
},
{
"name": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "0 new citation(s), enforcement report-only then exit 0"
},
{
"name": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"
},
{
"name": "pnpm check:doc-types (derived: a content/docs edit)",
"exit": 0,
"verdict": "Scanned 227 doc file(s) ... green"
}
],
"line_budget": "n/a — no skills/** or line-ratcheted ledger touched. This delta is +8/-2 over 2 files (git diff --stat aa16a9c..a9b00b0).",
"deviations": [
"The changeset is unchanged: its text still matches the behaviour, and a README sentence is not published source under check-changeset-presence's rule.",
"The PR body still cites aa16a9c as the tested head. The body is not edited in this round; the new head is a9b00b0, and the delta is docs only."
],
"files_changed": [
"content/docs/guide/fields.md",
"packages/fields/README.md"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
ACCEPT: PR objectui#12219 (head
a9b00b0f47) ·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T09:41ZReviewed against GitHub and the diff, not the reports (
os-dev-report6121612387and round 26121684948). Claim6120881363, amended in6121641360.Verdict
main. The first line isFixes #12200, andClause-②: nosits at a line start.os-steve. No model identifier.check-governed-merges: NOT governed.RegistryRefField.tsxbuilds oneSetfrom the spec'sAUDIENCE_ANCHOR_POSITIONS. One helper,nonGrantableNames(type), answers it forpositionand nothing for any other registry.permission-refis unchanged./identityat the17.6.0floor and at the installed17.7.0. No server companion is owed./identityimport put the eagerui-componentschunk over its ceiling by 6.1 KB (check:eager-closureexit 1).check:spec-floorsreport@object-ui/fieldsas an opaque subpath on the publish lane.@objectstack/spec, the form@object-ui/authalready uses forBUILTIN_MEMBERSHIP_ROLES, moves the eager closure by −5 bytes and is judged clean. It leavesui-components0.7 KB under its ceiling.maindeclares noposition-reffield. feat(plugin-security,platform-objects): grant forms pick a position or permission set by name from the catalog (#15204 B2) objectstack#22939's head declares one,sys_user_position.position, where the server refuses an anchor. So the filter belongs in the widget, and no declaration is owed.The restores are blob-equal.
position-ref"leaves out the audience anchors … because the server refuses an anchor as a grant target; a stored anchor still shows as its own option". Each matches the code and the pins, and nothing else in either file moved.@object-ui/fieldspatch. No export, prop or type change.Testonaa16a9cis the concurrency cancel of a superseded head ("Canceling since a higher priority waiting request … exists" on all eight shards). It is no reading of this change. The seat enqueues once every check ona9b00b0issuccessor skipped by design.Out-of-scope findings (carrier: this record; none filed)
plugin-securitydelegated-admin-gate.tskeeps the anchors as a literalSet(['everyone', 'guest'])and does not import the spec'sAUDIENCE_ANCHOR_POSITIONS, which other objectstack readers do. The values agree today, so this is drift waiting to happen, not a wrong answer.check-spec-range-floors.mjsdoes not parse a dynamicimport(), so a symbol taken only lazily is never judged against the floor. This was measured on this card's superseded lazy commit8bd8a14. Nothing is wrong onmaintoday.Landing: ready and auto-merge through the merge queue once
a9b00b0is green. The browser recheck rides objectstack-ai/objectstack#22939 and the next pin bump.Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Landed: PR objectui#12219 →
maina491b64·domain:uiseat 1 ·session_016djJF12Qt14ejKR5Vjt4sK· 2026-10-12T10:24Z6c437b0. The card closed ascompletedthroughFixes #12200. Read in this act: nothing else closed with it.a491b64to the accepted heada9b00b0f47, which ACCEPT6121707635covers.mainnow:position-refgrant picker leaves out the audience anchors (everyone,guest), read from the spec'sAUDIENCE_ANCHOR_POSITIONS, not from a hard-coded list. The server refuses an anchor as a grant target.permission-refand every other registry picker are unchanged.content/docs/guide/fields.mdand thefieldsREADME say so in one sentence each.position-reffield, and the next objectui pin bump.6121707635's "Out-of-scope findings" section, and neither is a wrong answer onmaintoday.delegated-admin-gate.tskeeps its own literal anchor set.check-spec-range-floors.mjsdoes not parse a dynamicimport().pm:dispatchedis removed in this act.domain:uiandpriority:p1stay.Generated by Claude Code