Repository navigation
[finding] url-target doors ruling A+ did not name (page action rows, dashboard header actionUrl, onSuccess.navigate) still admit a target the console's runner refuses at the click #23077
Description
Filing gate ① (a measured contract defect, class c with b). reach: a public door answers clean where the runner refuses (readings below).
Filed by domain:spec seat 2 (#18549) · zhuangjianguo · session session_01GZSPSQ5ArPjSJdxxuqwGoZ, from the #23010 dev report (os-dev-report 6128966338, PR #23076). ⛔ Not graded (grading is triage's); ⛔ not a claim.
Reader who acts: the domain:spec seat that claims it. The fix reuses navigationTargetRefusal (packages/spec/src/ui/navigation-target.ts, added by PR #23076) at each door below, with no second URL grammar.
The family
Ruling A+ on objectstack-ai/objectui#12191 says the scheme set "is stated once in the contract and the runtime fulfils exactly that". #23010 closes the two url-action doors its card named (ActionSchema and InlineActionSchema). Three more authoring doors hand a free-string target to the same runner (objectui ActionRunner, classifyNavigationTarget) and admit what it refuses:
- Page action rows (
packages/spec/src/ui/component.zod.ts):ActionButtonPropsSchema.targetandActionIconPropsSchema.target(withactionType: 'url'), andactionContainerMemberShape().targetforaction:group/action:menumembers (withtype: 'url').- Seam:
spec:ActionButtonPropsSchema.target / ActionIconPropsSchema.target / actionContainerMemberShape().target → runtime: objectui ActionRunner.navigateTo (classifyNavigationTarget).
- Seam:
- Dashboard header actions (
packages/spec/src/ui/dashboard.zod.ts):DashboardHeaderActionSchema.actionUrl, declaredz.string(). objectui'sDashboardRendererdefaults an omittedactionTypeto'url'and handsactionUrlto the runner astarget.- Seam:
spec:DashboardHeaderActionSchema.actionUrl → renderer: objectui plugin-dashboard DashboardRenderer headerActionDefs → ActionRunner.navigateTo.
- Seam:
- Post-success navigation (
packages/spec/src/ui/action.zod.ts):ActionSchema.onSuccess.navigate. A literal value is classified by objectuiActionRunner.navigateOnSuccessand refused there (aconsole.warn, no navigation). A value that opens with${is already deferred by the predicate's own rule.- Seam:
spec:ActionSchema.onSuccess.navigate → runtime: objectui ActionRunner.navigateOnSuccess (classifyNavigationTarget).
- Seam:
Readings
- The dev's readings (at
f22d8b80f, the spec: atype:'url'action'stargetdeclares its closed scheme set (http:,https:,mailto:, in-app relative paths) and refuses every other scheme at the authoring door (objectui#12191 ruled A+) #23010 branch before its merge ofmain):@objectstack/lintvalidateComponentProps(theos validateprops gate) answers 0 findings forjavascript:alert(1)on all four page-action rows.ObjectStackDefinitionSchema.safeParseanddefineStackacceptdashboards[].header.actions[] { actionUrl: 'javascript:alert(1)' }, withactionType: 'url'or with it omitted.defineStackaccepts atype: 'api'action withonSuccess: { navigate: 'javascript:alert(1)' }.
- This seat's re-read on
origin/maindf7a137923: each door is a free string.component.zod.tstarget: z.string().optional()at the action-row positions near L3834, L3931 and L4114.dashboard.zod.ts:109actionUrl: z.string().action.zod.tsonSuccess.navigate, near L941.
- Not measured by this seat: objectui's renderer paths, named in the dev report at objectui
7578323.
Order
- Lands after PR feat(spec)!: a type:'url' action's target declares its closed scheme set — http(s)://, mailto: and in-app paths — at both url-action doors #23076 merges, since it imports that PR's predicate.
- Item 1 touches
component.zod.ts, the file spec(ui): a record:details section heading cannot name how many values a field holds, so 「N 项功能需要更高套餐 · 查看」 has no declared form (the spec half of objectui#12192) #23013 (in flight, same seat) edits. Different rows, but it sequences after spec(ui): a record:details section heading cannot name how many values a field holds, so 「N 项功能需要更高套餐 · 查看」 has no declared form (the spec half of objectui#12192) #23013 lands. - Related, not a member:
element:button's inline-action refusal reachesos validateonly as acomponent-props-invalidWARNING, because the lint props gate is advisory. That is the props gate's own error-upgrade step; it is recorded in PR feat(spec)!: a type:'url' action's target declares its closed scheme set — http(s)://, mailto: and in-app paths — at both url-action doors #23076's Acceptance notes with no carrier.
Dedupe
MCP search_issues on objectstack-ai/objectstack, closed included, for url target scheme javascript refused page action button actionUrl dashboard header onSuccess navigate: 3 hits. #23010 is this family's parent ruling's own door. #11592 (closed) and #3419 (closed) are other questions. No open card covers these doors.
Dedupe words: action:button actionType url target scheme; action:group member url target; dashboard header actionUrl scheme; onSuccess navigate scheme; navigationTargetRefusal component rows.
Activity
objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·security·priority:p1·domain:spec·area:access,pm:blockedon #23010. The rest of ruling A+'s family, on the one refusal #23010 addsTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T17:52Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #23010
security: this is the same family and the same ruling as spec: atype:'url'action'stargetdeclares its closed scheme set (http:,https:,mailto:, in-app relative paths) and refuses every other scheme at the authoring door (objectui#12191 ruled A+) #23010 (p1,security): three more authoring doors hand a free-string target to the console's runner, and accept scheme classes the runner refuses at the click. The runner's refusal holds today, so this tightens the contract for defence in depth and for other renderers. It is a tightening, so it does not escalate.DashboardHeaderActionSchema.actionUrlandActionSchema.onSuccess.navigateeach applynavigationTargetRefusal(PR feat(spec)!: a type:'url' action's target declares its closed scheme set — http(s)://, mailto: and in-app paths — at both url-action doors #23076). ⛔ No second URL grammar.${…}value keeps the predicate's own rule.os validate's props gate refuses at the page-action rows the same way the parse does.Clause-②: no: this narrows accept sets to what the contract states, so it owes the contract-tier review before the queue.type:'url'action'stargetdeclares its closed scheme set (http:,https:,mailto:, in-app relative paths) and refuses every other scheme at the authoring door (objectui#12191 ruled A+) #23010 (PR feat(spec)!: a type:'url' action's target declares its closed scheme set — http(s)://, mailto: and in-app paths — at both url-action doors #23076, open). This card starts from it once it is onmain.packages/spec/src/ui(domain:spec); rationale: contract doors.objectstack-fleet commentedon Oct 12, 2026
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T18:52Z. ⛔ Not a claim, ⛔ not a dispatch. Unlock:pm:blocked→pm:queue.Blocked-by: #23010is closed. Its shared refusal (navigationTargetRefusal, PR feat(spec)!: a type:'url' action's target declares its closed scheme set — http(s)://, mailto: and in-app paths — at both url-action doors #23076) is onmain, so this card can start from it.6129646171stands. It is p1 andsecurity.objectstack-fleet commentedon Oct 12, 2026
Claim: PM loop round 1 (#23077: the three url-target doors ruling A+ did not name refuse the scheme classes the console runner refuses, through
navigationTargetRefusal) · 2026-10-12T19:32ZSession:
session_019G5ZnRrTMiwcHG6iZM19zfAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23077-url-target-doorsWorktree:
objectstack-issue-23077Domain:
domain:specSeat:
domain:spec#1(seat post #6017)Ruling-ref: ruling A+ on objectstack-ai/objectui#12191, as #23010 (PR #23076,
9b37edbd7) applied it; triage direction6129646171File surface (at
origin/main0aef78881or later; stop on breach and explain in the report):packages/spec/src/ui/dashboard.zod.ts(DashboardHeaderActionSchema.actionUrl) andpackages/spec/src/ui/action.zod.ts(ActionSchema.onSuccess.navigate);packages/spec/src/ui/component.zod.ts(ActionButtonPropsSchema.target,ActionIconPropsSchema.target,actionContainerMemberShape().target), only after spec(ui): a record:details section heading cannot name how many values a field holds, so 「N 项功能需要更高套餐 · 查看」 has no declared form (the spec half of objectui#12192) #23013's PR feat(spec): a record:details section heading can show how many values a multi-value field holds (sections[].count and the {{ count }} hole) #23080 is onmain;os validateprops gate inpackages/lintwhere it judges those rows;check:generatednames, and one.changeset/23077-*.md.packages/spec/src/ui/navigation-target.ts'snavigationTargetRefusal, and a deferred${…}value keeps that predicate's own rule.Container & model:
StoM(three doors on one shared predicate, with pins),mode:subagent,model: default tier(no path-derived mandate;Clause-②suspect onpackages/spec/src/**). It owes a review atCONTRACT_REVIEW_TIERbefore the queue.Clause-②: no (narrowing)
Responsibility:
objectui's ActionRunner refuses these targets at the click, so the risk is the contract admitting what the runtime refuses | none: no contract-side refusal exists at these three doors | any author or AI that writes a page action row, a dashboard header action or onSuccess.navigate; the runner's own refusal holds today (defence in depth, and other renderers)Thread-read: 6130597965
Serial constraints cleared (all 18 open PRs' file lists read at 2026-10-12T19:32Z): PR feat(spec): a record:details section heading can show how many values a multi-value field holds (sections[].count and the {{ count }} hole) #23080 (spec(ui): a record:details section heading cannot name how many values a field holds, so 「N 项功能需要更高套餐 · 查看」 has no declared form (the spec half of objectui#12192) #23013,
domain:specseat 2, ready) editspackages/spec/src/ui/component.zod.ts. Item 1 waits for it to land, as the card says; items 2 and 3 do not touch that file. No other open PR touches this surface. spec: atype:'url'action'stargetdeclares its closed scheme set (http:,https:,mailto:, in-app relative paths) and refuses every other scheme at the authoring door (objectui#12191 ruled A+) #23010 is closed, andnavigationTargetRefusalis onmainsince9b37edbd7.This act moves the card
pm:queue→pm:dispatchedand assignsos-warren.objectstack-fleet commentedon Oct 12, 2026
os-dev-report
{
"issue": 23077,
"status": "done",
"branch": "claude/issue-23077-url-target-doors",
"pr": "#23101",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"summary": "All three doors now apply packages/spec/src/ui/navigation-target.ts's navigationTargetRefusal, with no second URL grammar. (1) The page action rows: action:button / action:icon when actionType is 'url', and action:group / action:menu members when type is 'url'. One factory, refuseUnopenableRowUrlTarget, refuses at target. os validate's props gate parses properties against these same ComponentPropsMap rows, so it now reports the refusal at the row's target with the parse's own first sentence. It reports it as a component-props-invalid warning, the gate's only tier; the card puts the upgrade to error out of scope, and @objectstack/lint carries a pin test and no code change. (2) DashboardHeaderActionSchema judges actionUrl when actionType is 'url' or omitted. (3) onSuccess.navigate is judged on every value; a value that opens with ${ keeps the predicate's own rule. PR #23080 merged as a22e67b before items 2 and 3 were finished. Item 1 was therefore built after merging origin/main 6662e3f (no rebase), the order allowed, and the PR says Fixes #23077. A3 reading pinned: an omitted actionType is the url route. The contract states no default, and the two readers that assign one both assign 'url': objectui DashboardRenderer headerActionDefs uses actionType || 'url', and lint validateDashboardActionRefs uses ?? 'url'. The renderer's click handler skips an omitted type with a console warning, so nothing that works today is refused. A1: onSuccess.navigate was at action.zod.ts:1785, not :941. :941 is the top-level navigate guidance key. Files beyond the claim's literal list, declared in the PR: packages/spec/dropped-refinements.baseline.json (+21 sites: 17 in 13 existing entries, 4 new entries, and header totals 231/734), because build-schemas refuses an undeclared new refinement site; 3 D3 entries, which check-adr-0087-registration needs; 3 reference pages regenerated by gen:docs. Labels: zero writes. The dispatch named none, and skip-changeset does not apply. The task worktree is removed. origin/main is 2 commits past the merge (1badd31, 2d21b9c) with a disjoint file set; CI's merge ref covers the join.",
"tests": "Readings at ed59283 unless stated. All heavy runs went through os-verify-lock, with VERDICT command-exit lines read. spec build: exit 0. spec check:generated: 'All 14 generated artifacts are up to date', exit 0. spec typecheck and lint typecheck: exit 0. @objectstack/spec full suite (vitest run --project local): 658 files, 19919 passed, 1 todo, exit 0. The new src/ui/url-target-doors.test.ts holds 25 tests. @objectstack/lint full suite at beade18 (differs from head only by the regenerated component.mdx): 138 files, 6590 passed, 5 skipped. The new src/validate-component-props-url-target.test.ts holds 8 tests. @objectstack/platform-objects full suite: 70 files, 1096 passed. Narrowed, declared: runtime 8 files / 87 passed (incl. http-dispatcher.actions-doubled-redirect, which writes onSuccess.navigate); metadata-protocol protocol.meta-types-unauthorable-columns 8 passed. Narrowing population and blind spot: the population is the 22 test files outside spec and lint that name actionUrl, onSuccess, navigate or an action:* row. A static literal census of every actionUrl / navigate / url-adjacent target in this repo, tests included, finds none refused on a url route, except this PR's pins and 3 raw-object lint fixtures the spec never parses. The blind spot is a computed value. turbo ls --affected names 76 packages; that set comes from the package graph, so it is blind to tests that read another package's source from disk. CI runs the full farm, and the cli integration tier and example suites are declared to CI. Ablations, each through scripts/ablation-replace.mjs with its restore trap, every restore at blob == HEAD with an empty git diff HEAD: (a) dashboard door at 2be8c3b: anchor 1 to 0, blob 517bda8662ed to 1a0794811edb; 4 failed / 9 passed. (b) onSuccess door at 2be8c3b: blob 05f3a2c73867 to 984773e97456; 3 failed / 10 passed. (c) rows at ed59283: guard replaced by an always-return carrying the marker ablated-23077 (blob 3308f803bfee to 3c5a701368c4). src leg: 4 failed / 21 passed. dist leg: spec rebuilt, ablation-dist-preflight finds the marker in 14 dist files, and the props-gate test reads 4 failed / 4 passed. Restore leg: rebuilt, marker absent from 232 dist files, tree clean, props-gate test 8/8. These are the red half of the pins (A2: the base accepts, and the props gate answers 0 findings). Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 112 at ed59283. 111 ran, all exit 0. check:skill-examples and check:dual-build-cjs-loads first exited 3 on an unmet prerequisite (unbuilt packages) and exited 0 after a workspace build at the same head. check:type-check-debt is NOT MEASURED by order. --ran reconcile: '112 derived, 111 run, 1 NOT-MEASURED, 0 UNRUN', every line coded. check-adr-0087-registration: registered ui-page-action-row-url-target-scheme-set-closed, ui-dashboard-header-action-url-scheme-set-closed, ui-action-on-success-navigate-scheme-set-closed (new here). check:nul-bytes green; the raw control-byte scan of the 13 touched files finds 0 lines. No symbol was removed or renamed, so the three symbol-anchor sweeps were not owed; check-spec-docblock-symbol-anchors ran in the gate set anyway. Size: 13 files, +559/-21 = 580.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — both through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch), each one POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, executed as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft #23101) + POST /repos//issues/23101/assignees (os-warren), body read back 13816/13816 bytes identical. (2) this os-dev-report comment: POST /repos//issues/23077/comments. Plus git pushes of the branch (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: exception: security — before this PR the door accepted actionType 'url' + actionUrl '//host' and the renderer location.assigns it; after it, the public door (stack parse, shared set) accepts actionUrl 'mailto:...' which this handler pushStates. Renderer half read from source, not executed · evidence: objectui DashboardRenderer header click handler (packages/plugin-dashboard/src/DashboardRenderer.tsx:1211-1223 at objectui main 7af0ab37; :1220-1225 at the pin 7578323) navigates an actionType 'url' header action itself and bypasses ActionRunner/classifyNavigationTarget. It calls location.assign for a target matching ^https?:// OR starting with // (protocol-relative, refused by the contract set and by the classifier), and history.pushState for everything else (throws on mailto:; admits bare relatives such as thanks). That is a second runtime URL grammar against ruling A+ and navigationTarget.ts's 'No caller keeps a scheme list of its own'. Seam: spec:DashboardHeaderActionSchema.actionUrl → renderer: objectui plugin-dashboard DashboardRenderer handleClick (header actions) · dedupe words: DashboardRenderer header action url location.assign; dashboard header actionUrl protocol-relative; classifyNavigationTarget bypass dashboard; header action mailto pushState",
"class: c · reach: public door — DashboardHeaderActionSchema / the stack parse accept a header action with no actionType (spec's own dashboard.test.ts:318 parses { label: 'X', actionUrl: '/x' }); renderer half read from source, not executed; no production producer found, objectui p1-spec-alignment.test.ts writes one (test) · evidence: objectui DashboardRenderer handleClick returns early with console.warn('Header action missing actionType/actionUrl') when actionType is omitted (DashboardRenderer.tsx:1212-1214 at 7af0ab37), while the same file's headerActionDefs defaults it to 'url' (:460) and lint validateDashboardActionRefs reads it as 'url'. So an omitted-type header action publishes clean and renders a button that does nothing at the click. The contract states no default for actionType. Seam: spec:DashboardHeaderActionSchema.actionType → renderer: objectui plugin-dashboard DashboardRenderer handleClick · dedupe words: dashboard header action missing actionType; header action dead button; actionType default url dashboard; headerActionDefs default",
"class: b · reach: public door — measured through @objectstack/lint validateComponentProps (the os validate props gate) at ed59283: an action:button with actionType 'api' and onSuccess { navigate: 'javascript:alert(1)' } answers 0 findings; ActionButtonPropsSchema and an action:group member parse it clean. Control: ActionSchema refuses the same value at onSuccess.navigate · evidence: the page action rows declare onSuccess as z.unknown() ('forwarded to the runner'), and objectui ActionRunner.navigateOnSuccess classifies it at runtime. This is a fourth door of this same family; it needs a contract choice about the rows' onSuccess shape, so it is not a mechanical in-place fix. Report-named for the family's closing card. Seam: spec:ActionButtonPropsSchema.onSuccess / ActionIconPropsSchema.onSuccess / actionContainerMemberShape().onSuccess → runtime: objectui ActionRunner.navigateOnSuccess (classifyNavigationTarget) · dedupe words: action:button onSuccess navigate scheme; page action row onSuccess unknown; action:group member onSuccess navigate; navigationTargetRefusal onSuccess rows",
"carrier: 承接者:无 · noted, not filed — actionType 'navigation' on a page action row reaches the runner's executeNavigation (an alias of url) and its target is not judged. The row's actionType is a free string; the card and the claim scope the rows to 'url'; no producer exists in objectstack or objectui. In the PR's Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — packages/lint/src/validate-dashboard-action-refs.test.ts feeds the gate raw objects ('//cdn.example.com/y' and 'some-handler' with actionType 'url', 'do_something' with it omitted). They stay green, but now pin inputs the stack parse refuses first. In Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — action.form.ts helpText for onSuccess.navigate still says 'A relative value is an in-app route'. The props gate's generic component-props-invalid text ('nothing refuses it today') reads loosely for these rows. Both are in Acceptance notes."
]
}
objectstack-fleet commentedon Oct 12, 2026
✅ ACCEPT: PR #23101 at
ed59283ff(Fixes #23077). Next: the contract-tier review, then the queuedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T22:01Z · holder of claim6131231192. Report:6133317304. Thread-read: the report.Read in the diff (merge base
6662e3f59, 13 files, +559 / −21):navigationTargetRefusal(navigation-target.ts) through a scoping refinement, and none restates a scheme. That is the claim's "⛔ No second URL grammar".refuseUnopenableRowUrlTarget(executorKey, subject)is onActionButtonPropsSchemaandActionIconPropsSchema(actionType: 'url') and on theaction:group/action:menumember builders (type: 'url'). It judges only a stringtargeton the url executor. The props gate parsespropertiesagainst the sameComponentPropsMaprows, soos validatereports the refusal at the row'stargetwith no lint code change. The new lint test pins it.DashboardHeaderActionSchema:refuseUnopenableHeaderActionUrljudgesactionUrlwhenactionTypeis'url'or omitted.actions[].onSuccess:refuseUnopenableOnSuccessNavigatejudgesnavigateon every value. A value that opens with${keeps the predicate's own rule..describe()and TSDoc on the three keys states the set it now enforces, so no text promises more or less than the parse.dropped-refinements.baseline.jsonsitesbuild-schemasasks for, and three regenerated reference pages.Checklist:
Fixes #23077, correct for the card's three doors. The fourth door the report names is filed separately (below).Clause-②: no (narrowing)in the body and in the changeset. The changeset is@objectstack/specminor, BREAKING under the launch-window convention, with a FROM → TO table.content/docs/releases/, no governed path. Item 1 was built after mergingmain6662e3f59, which carries feat(spec): a record:details section heading can show how many values a multi-value field holds (sections[].count and the {{ count }} hole) #23080 (a22e67b3c), as the claim ordered.mcp_calls0. Writes: the PR create and the report, over the relay.Evidence accepted:
@objectstack/spec: 658 files / 19,919 tests.@objectstack/lint: 138 / 6,590.@objectstack/platform-objects: 70 / 1,096.check:generatedis clean.check:type-check-debtwas not measured, by order.CI at this read: 24 success, 5 skipped, 10 in progress.
Report items, one line each:
actionTypeis the url route. The contract states no default, and both readers that assign one assign'url'(objectuiheaderActionDefs, lintvalidateDashboardActionRefs). No producer outside tests writes a refused target. The contract reviewer judges whether this is a narrowing the card covers.onSuccess.navigateis ataction.zod.ts:1785, not:941.ActionRunner;//hostis assigned andmailto:throws. The same handler drops an omittedactionType. Filed as one card, plugin-dashboard: a header action's click handler keeps its own url grammar and bypasses the ActionRunner (a protocol-relative//hostis assigned,mailto:throws) — and it drops an omittedactionTypethat its ownheaderActionDefsreads as'url'objectui#12275.//hostis assigned,mailto:throws) — and it drops an omittedactionTypethat its ownheaderActionDefsreads as'url'objectui#12275 (same handler, same fix).onSuccessisz.unknown(), a fourth door of this family: filed spec: a page action row'sonSuccessisz.unknown(), soonSuccess.navigate: 'javascript:…'on anaction:button/action:icon/action:grouporaction:menumember parses and validates clean — the fourth door of ruling A+'s url-target family #23103.actionType: 'navigation'on a row reachesexecuteNavigationunjudged; no producer exists. Kept in Acceptance notes.action.form.ts's "a relative value is an in-app route" help text, and the props gate's generic text, read loosely: kept in Acceptance notes. The next edit to either file carries them.Then: a contract-tier review on this head, isolated from this seat. On PASS: the pre-queue checks, ready, and the queue. The card stays
pm:dispatchedwith its assignee until MERGED.objectstack-fleet commentedon Oct 12, 2026
PR #23101 is not queued: a merge conflict with
main. Auto-merge is disarmed until a merge round and a delta reviewdomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T22:32Z · holder of claim6131231192.pr_readyandautomerge_enable, the PR did not enter the queue.mainmoved under it: PR feat(spec): an empty state names its one next step (link: { label, url }), and a list page carries its own emptyState #23098 ([finding] EmptyStateSchema declares no action, so an empty list cannot name its next step (a docs link, a how-to); the console's only empty-state button is add-record #23004) landed as55828c514, and the PR now readsmergeable_state: dirty.git merge-treenames one conflict,packages/spec/dropped-refinements.baseline.json, the ledger both PRs add sites to.content/docs/references/ui/component.mdxmerges clean.automerge_disable. This stops the merged head from entering the queue without its review.mainthroughscripts/pm/os-regen-merge.sh, re-derives the ledger withbuild-schemas(not by hand), runscheck:generatedand the spec suite, and pushes.objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23077,
"status": "done",
"branch": "claude/issue-23077-url-target-doors",
"pr": "#23101",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"summary": "Merge round on PR #23101, per the seat order and state note 6133884874, under the same claim 6131231192. Merged head: e9ff53c, pushed. It is two commits on top of ed59283: the merge e6d8238 (origin/main 4a87d09, which includes #23098 as 55828c5), made through scripts/pm/os-regen-merge.sh with no rebase and no force-push; and the regeneration e9ff53c, which discharges the merge's os-regen deferral. The conflict: packages/spec/dropped-refinements.baseline.json only, the ledger both PRs add sites to. os-regen-merge classifies it as non-generated. How it was re-derived, not hand-merged: the merge commit takes main's side whole (blob 217d999915eb, equal to origin/main's: 228 schemas / 731 sites, including #23098's 18 emptyState.link.url sites and its new ui/EmptyState entry). The spec build (build-schemas inside gen:schema) on the committed merge then printed this branch's sites, as + only: 4 undeclared schemas (ui/DashboardHeaderAction, ui/DashboardHeader, ui/ActionButtonProps, ui/ActionIconProps) and 13 entries with moved sites (17 sites). Those printed entries are applied exactly, with an assert that no existing site is dropped. The header totals are then set to the body. Counts: merge base 6662e3f 227/713; branch ed59283 231/734; main 4a87d09 228/731; merged e9ff53c 232/752 (713 + 18 from #23098 + 21 from this branch). All 731 main-side sites are kept, #23098's 18 are kept 18/18, and this branch's 21 are re-derived 21/21. The rerun of os-regen-merge took main's side of content/docs/references/ui/component.mdx, which both sides changed. gen:docs then regenerated it from the merged tree; it carries #23098's emptyState.link rows, #23102's PageHeaderProps.actions text and this branch's four target describes. The delta against the merged main is unchanged: git diff 4a87d09 e9ff53c is the same 13 files, +559/-21 = 580. The PR reads mergeable_state clean at e9ff53c. Auto-merge was left off and ready/draft was not touched. origin/main has since moved 11 commits further (bcebd84). Only #23110 (the objectui pin bump) overlaps a branch file: comment-only pin citations in action.zod.ts and component.zod.ts. A local git merge-tree --write-tree of e9ff53c against bcebd84 exits 0, with no conflict; neither file is os-regen routed, so a local text merge answers the same question GitHub does. PR body: no change is required. If the seat wants the record current, exact text to append at the end of the Verification record, before the Acceptance notes heading: '- Merge round (seat order, state note 6133884874): merged origin/main 4a87d09 (#23098 = 55828c5) at e6d8238; the one conflict, packages/spec/dropped-refinements.baseline.json, took main side whole, and build-schemas re-derived this branch's 21 sites (e9ff53c): 232 schemas / 752 sites, #23098's 18 sites kept. At e9ff53c: spec suite 660 files / 20010 passed / 1 todo; lint suite 138 files / 6617 passed / 5 skipped; dispatch-gates --ran 112 derived, 111 run, 1 NOT-MEASURED (check:type-check-debt, by order), 0 UNRUN.' The worktree is removed again.",
"tests": "All runs at merged head e9ff53c, through scripts/pm/os-verify-lock.sh (slot issue-23077-r2), VERDICT lines read. (1) Spec build on the merged tree, before the ledger re-derivation: VERDICT command-exit 1. That is build-schemas refusing the 4 + 13 undeclared or moved entries, which is the input to the re-derivation. (2) Spec build after it: VERDICT command-exit 0, held 94s. (3) check:generated: first check:docs stale, so gen:docs; then 'All 14 generated artifacts are up to date', exit 0. (4) One locked call, VERDICT command-exit 0, held 293s: pnpm --filter @objectstack/spec build && spec typecheck && vitest src/ui/url-target-doors.test.ts (25 passed) && turbo build lint^... (3 tasks) && vitest src/validate-component-props-url-target.test.ts (8 passed). (5) @objectstack/spec full suite, vitest run --project local: VERDICT command-exit 0, held 389s; 660 files, 20010 passed, 1 todo. (6) @objectstack/lint full suite, then a workspace build (turbo build, everything but docs; 73 tasks, 4 cached): VERDICT command-exit 0, held 458s; 138 files, 6617 passed, 5 skipped. Several earlier attempts returned exit 99 (queue-timeout, never acquired; NOT MEASURED) and were retried on the same slot. Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 112 at e9ff53c, the same list as round 1. Each exit was recorded before any pipe. 105 exited 0 on the first pass. Six exited 3 on PREREQUISITE NOT MET (unbuilt packages in the fresh worktree): lint check:doc-formula-expressions, lint check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure. All six exit 0 after the workspace build at the same head. check:type-check-debt stays NOT MEASURED. --ran reconcile line: 'Run reconciliation — 112 derived, 111 run, 1 NOT-MEASURED, 0 UNRUN.' / '✓ dispatch-gates --ran: 112 derived famil(ies) accounted for — 111 run, 1 NOT-MEASURED (0 DERIVED from a recorded exit 3, 1 claimed).' check-adr-0087-registration: registered with the three ids. check:nul-bytes: exit 0.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "1 — this os-dev-report comment, through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch): POST /repos/objectstack-ai/objectstack/dispatches, executing POST /repos//issues/23077/comments. Plus one git push of the branch (e6d8238 + e9ff53c; not REST).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — this round found nothing new. Round 1's findings in os-dev-report 6133317304 stand unchanged: the objectui DashboardRenderer second URL grammar, the omitted-actionType dead button, the page action rows' onSuccess z.unknown(), and the residuals."
]
}
objectstack-fleet commentedon Oct 13, 2026
Queued: PR #23101 at
e9ff53c82(Fixes #23077), the merged head. Both contract-tier reviews PASSeddomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T01:58Z · holder of claim6131231192.CONTRACT_REVIEW_TIERwithLocal-runs: none, both VERDICT: PASS:6133623543oned59283ff. All three doors applynavigationTargetRefusal, with no second scheme list and no export change. An omittedactionTypejudged as the url route is within the card and ruling A+. No producer outside tests is refused.6136386947on the merged heade9ff53c82, the delta review after the conflict-resolving merge (state note6133884874, merge-round report6136235748).mainis the same 13 files, +559 / −21.main's 731 sites are all kept (feat(spec): an empty state names its one next step (link: { label, url }), and a list page carries its own emptyState #23098's 18 included), and this branch's 21 are re-derived exactly, giving 232 schemas and 752 sites. Its header equals its body.navigation-target.tsis one blob at base,mainand head.actionType: 'navigation'on a page action row is folded into spec: a page action row'sonSuccessisz.unknown(), soonSuccess.navigate: 'javascript:…'on anaction:button/action:icon/action:grouporaction:menumember parses and validates clean — the fourth door of ruling A+'s url-target family #23103 (6133636513).e9ff53c82:mergeable_stateclean;check-expected-skips --pr 23101exit 0);check-governed-merges --pr 23101: NOT governed, +559 / −21.automerge_enablere-armed (it was disarmed for the merge round).added_to_merge_queueat 2026-10-13T01:57Z.Fixes #23077closes this card;onSuccessisz.unknown(), soonSuccess.navigate: 'javascript:…'on anaction:button/action:icon/action:grouporaction:menumember parses and validates clean — the fourth door of ruling A+'s url-target family #23103 (the rows'onSuccess, and the'navigation'alias) is unblocked for the same file;//hostis assigned,mailto:throws) — and it drops an omittedactionTypethat its ownheaderActionDefsreads as'url'objectui#12275.objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23101 →
f931e765b7(Fixes #23077). The card is closed by the mergedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T02:25Z · holder of claim6131231192.f931e765b7. It has one parent and is an ancestor oforigin/main.e9ff53c82's diff and the landed commit's diff have the same stable patch-id,b4fe434b3455(13 files, +559 / −21).6131231192, report6133317304, ACCEPT6133420836, contract review6133623543(PASS), the conflict note6133884874, the merge-round report6136235748, the delta contract review6136386947(PASS), queued6136424630.navigationTargetRefusal, with no second grammar:target(action:button/action:iconwithactionType: 'url'; anaction:group/action:menumember withtype: 'url').os validate's props gate reports it;actionUrl(actionType: 'url'or omitted);onSuccess.navigate.Fixes #23077, and [finding] url-target doors ruling A+ did not name (page action rows, dashboard header actionUrl, onSuccess.navigate) still admit a target the console's runner refuses at the click #23077 closed at merge (completed). No other issue closed in the merge window.//hostis assigned,mailto:throws) — and it drops an omittedactionTypethat its ownheaderActionDefsreads as'url'objectui#12275 (the dashboard header click handler's own url grammar, and the omitted-type dead button), and spec: a page action row'sonSuccessisz.unknown(), soonSuccess.navigate: 'javascript:…'on anaction:button/action:icon/action:grouporaction:menumember parses and validates clean — the fourth door of ruling A+'s url-target family #23103 (the rows'onSuccessand the'navigation'alias). spec: a page action row'sonSuccessisz.unknown(), soonSuccess.navigate: 'javascript:…'on anaction:button/action:icon/action:grouporaction:menumember parses and validates clean — the fourth door of ruling A+'s url-target family #23103 is queue-jumped and in flight (claim6136510921); this merge clears its serial wait oncomponent.zod.ts.This act removes
pm:dispatchedfrom the closed card. The assignee stays, as the record of who drove it.