Skip to content

feat(spec)!: a page action row's url target is judged on the executor the runner runs, its name leg included - #23289

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23168-row-effective-executor
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23168-row-effective-executor

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23168
Clause-②: no (narrowing)

The fifth door of ruling A+'s url-target family (objectstack-ai/objectui#12191: the scheme set "is stated once in the contract and the runtime fulfils exactly that"), on the card's name leg, the half triage 6139317450 ruled. A page action row's target is now judged against the executor the console's action runner actually runs. One grammar, navigation-target.ts's navigationTargetRefusal, through the url-target refinement PR #23101 added; no second URL grammar.

This PR leaves #23168 open. The card's other half, carrying ActionSchema's "onSuccess only beside api / script" to the rows' effective executor (position 2, 6138882596), is not in this diff. The dispatch's assumption 4 has a fork clause, and measurement tripped it: two objectui producers write a row with onSuccess beside a non-api / non-script executor. That half goes back to the PM for a decision. See the section below.

What changed

  • effectiveRowExecutor(row, executorKey) (packages/spec/src/ui/component.zod.ts) states the runner's resolution once: the row's executor key, or its name when that key is omitted or empty. It uses ||, as the runner writes it, not the ?? the triage text transcribes. An empty actionType falls through to name at the click ('' || 'url' is 'url'), so it falls through here too. The resolution-order pins hold this.
  • ROW_URL_NAVIGATOR_EXECUTORS is { 'url', 'navigation' }, the two builtinExecutors rows that reach navigateTo.
  • refuseUnopenableRowUrlTarget judges target whenever the effective executor is one of those two, whichever key spelled it. This applies on action:button, on action:icon, and on every action:group / action:menu member. The refusal is navigationTargetRefusal's own text. Only the subject's parenthetical names the spelling that resolved the executor:
  • ⛔ name is not refused as an executor spelling. A row whose name names any other handler (navigate_edit, convert_lead, flow, …) is untouched, and a declared executor key still outranks the name.
  • The name and target describes on the three shapes state the name leg. component.mdx is regenerated.
  • Added the D3 entry ui-page-action-row-name-leg-url-target-judged and the changeset .changeset/23168-page-action-row-name-leg-url-target.md: @objectstack/spec minor, BREAKING (accept-set narrowing, launch-window convention), Clause-②: no (narrowing), ADR-0087 registered, with a FROM → TO table and the one-line fix.
  • os validate's props gate (@objectstack/lint validateComponentProps) parses properties against these same rows, so it reports the refusal at properties.target / properties.actions.N.target with no lint code change. validate-component-props-url-target.test.ts gains the name-leg readings. validate-component-props.ts is untouched, which keeps clear of PR feat(lint)!: every component-props finding is an error at os validate, os build and os lint #23244's severity rework there.

The onSuccess half: the fork the dispatch's assumption 4 names

Position 2's premise holds. objectui's runner navigates twice for a navigating executor that also declares onSuccess.navigate. Read at the .objectui-sha pin 1fb1ced36243, byte-identical at objectui main 866763bd:

  • the first navigation: a url row runs executeUrl → navigateTo (ActionRunner.ts:1934; navigation reaches the same navigator through executeNavigation, :2227);
  • the second: execute then calls handlePostExecution (:1418) → readOnSuccessNavigation (:1640) → navigateOnSuccess (:1642).

A registered host handler takes the same post-hop (:1384-1387), so a host handler that navigates (navigate, navigate_create, navigate_edit) also navigates twice.

The fork. Applied verbatim, the rule refuses onSuccess beside every effective executor other than api / script. The census found two producers that write exactly that:

  1. objectui packages/components/src/renderers/action/__tests__/action-button-icon-inputs-11168.test.tsx:474-490 writes actionType: 'run', the recording host handler node() sets at :176-183 with handlers: { run }. Beside it sits onSuccess: { navigate: '/app/records/${result.id}' }, with and without openIn: 'newTab', on both action:button and action:icon.
    • At the runner: run answers { success: true, data: { id: '42' } } (:113). The hop then navigates once, to /app/records/42, and the test pins that. This is a host handler whose post-success hop works and reads ${result.*} from the handler's return.
    • This test mounts through the renderer and never parses the node through the spec.
  2. objectui packages/types/src/__tests__/held-public-block-arms-10872.test.ts:224 asserts that the installed spec's action:button / action:icon row accepts properties: { onSuccess: { navigate: '/tasks' } }, a row with no executor key and no name. It is the remedy target of that file's flat-onSuccess refusal.
    • At the runner: the executor resolves to '', no handler or built-in matches, and executeActionSchema answers "not executable" (:2118-2123). The result is unsuccessful, so the hop never runs.
    • The verbatim rule would refuse it, and this objectui test would go red at objectui's next spec bump. It would not red main here: the Console Pin Gate builds the console and runs no objectui tests.

The rest of the census found zero rows with onSuccess beside a non-api / non-script executor:

  • this repository's examples/**, packages/cli and packages/qa (dogfood); its only row onSuccess producers are spec and lint tests, all beside api;
  • objectui's app metadata at the pin;
  • hotcrm at 0f84c9c.

The dispatch says: ⛔ do not silently narrow to the navigating subset, and do not refuse the producer. So no onSuccess rule is in this diff. The options and the four-axis analysis are in the dev report on the card. The ruled half is independent of the fork, so it ships here.

Mechanism readings (the dispatch's assumptions)

  • A1 held, with one correction. ActionRunner.execute reads action.type || action.actionType || action.name || '' at :1265, at the pin 1fb1ced36243 and unchanged at objectui main 866763bd.
    • The renderers forward the executor key as the runner's type: action-button.tsx:286 type: schema.actionType, action-icon.tsx:167, action-group.tsx:350 type: action.type, action-menu.tsx:274.
    • The operator is ||, not ??, so the helper uses ||.
    • The registrations' defaultProps (actionType: 'script') are read only by the designer palette (PageDesigner.tsx:216), never at render, so an omitted key stays omitted.
    • The url navigator navigateTo is reached by exactly url and navigation. No host registers a handler under either name: ConsoleShell.tsx:97 and useConsoleActionRuntime.tsx:803 register api / flow / script / modal, and AppContent.tsx / useObjectActions.ts register crud_success, dialog_cancel, navigate_create, navigate_edit, create, delete, navigate and refresh.
    • Handlers win before built-ins. The legacy action.navigate fallback is a key the strict rows refuse.
  • A2 held. At 4f5679c26, actionType: 'navigation' was refused at actionType (feat(spec)!: a page action row's onSuccess is ActionSchema's own block, and its executor key refuses the 'navigation' alias #23164), and name: 'navigation' / name: 'url' with no executor key parsed clean with a javascript: target, on all four rows. actionType: '' with name: 'url' parsed clean too. The pins assert what the runner does for each spelling.
  • A3 held. validateComponentProps parses properties against ComponentPropsMap (validate-component-props.ts:372) and files every non-unrecognized_keys issue as component-props-invalid at its path. The lint pins read the new refusal there, from the rebuilt dist/.
  • A4 tripped its fork clause (above).
  • A5 held (above): two navigations, cited at the pin.
  • A6: the name leg's refusal is navigationTargetRefusal's, with feat(spec)!: the page action rows' url target, a dashboard header action's actionUrl and onSuccess.navigate apply the shared url-target refusal #23101's subject form. No new phrasing.

FROM → TO

row before after
action:button / action:icon with no (or an empty) actionType and name: 'url' or 'navigation' any target target judged by the url scheme set, refused at properties.target
an action:group / action:menu member with no (or an empty) type and name: 'url' or 'navigation' any target target judged by the url scheme set, refused at properties.actions.N.target
actionType: 'navigation' / member type: 'navigation' with a refused target refused at the executor key only refused at the executor key (unchanged) and at target
actionType: 'url' / member type: 'url' judged unchanged, same message

Producers: none. No page action row in this repository, in objectui (pin 1fb1ced36243, main 866763bd) or in hotcrm (0f84c9c) is named url or navigation.

Verification record

Readings are at f1a31cee7 unless stated. That head is this branch merged with origin/main 6d7f4df3c through scripts/pm/os-regen-merge.sh, with no conflicts. Every heavy run went through scripts/pm/os-verify-lock.sh, and each result is its VERDICT command-exit line.

  • Builds:

    • @objectstack/spec build: exit 0.
    • check:generated: "All 14 generated artifacts are up to date", exit 0. Before gen:docs it named only content/docs/references/**.
    • The dropped-refinements ledger did not move (759 sites / 234 schemas): this diff changes an existing refinement and adds no site.
    • Lint's dependency closure ('@objectstack/lint^...') and platform-objects' closure: exit 0.
    • The workspace build (turbo run build --filter=!@objectstack/docs --concurrency=2): 74 / 74.
  • Typecheck: @objectstack/spec typecheck exit 0 (at b76c26a99), and @objectstack/lint typecheck exit 0.

  • Tests:

    • @objectstack/spec, full (vitest run --project local), at b76c26a99: 668 files, 20254 passed, 1 todo, exit 0. The merge since brought nothing into packages/spec.
    • The new component-action-row-effective-executor.test.ts holds 44 tests. With url-target-doors.test.ts, component-action-row-on-success-executor.test.ts and component-props-unknown-members.pin.test.ts: 4 files, 163 passed.
    • @objectstack/lint, full: 138 files, 6686 passed, 5 skipped, exit 0. validate-component-props-url-target.test.ts holds 20 tests (8 before).
  • The importers, narrowed and declared. The run covered every test file outside spec and lint that names a page action row or ComponentPropsMap (6 files in 4 packages), plus platform-objects in full:

    • platform-objects: 70 files, 1096 passed;
    • mcp: 1 / 11;
    • cloud-connection: 1 / 16;
    • example-showcase: 3 files / 14.

    The blind spot is a test that builds such a row from computed values. CI runs the full farm.

  • Ablation, one-time, at b76c26a99, through scripts/ablation-replace.mjs with its restore trap. effectiveRowExecutor lost its name leg (the base behaviour), marked ablated-23168-name-leg.

    • Spec, src leg: 12 failed / 57 passed. These are exactly the 8 name-leg refusals and the 4 empty-key refusals; every control and all of url-target-doors.test.ts stayed green.
    • Lint, dist leg: after a spec rebuild, ablation-dist-preflight found the marker in 14 dist files. The lint file then read 4 failed / 16 passed: the four name-leg refusals at the gate.
    • Restore: blob == HEAD (a3d90ed32459) with an empty git diff HEAD. A spec rebuild followed, and --absent found the marker in none of the 232 dist files, with the tree clean.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 111 at f1a31cee7. Every exit was recorded before any pipe.

    • 110 ran, and every final exit is 0.
    • Eleven first exited 3 or 1 with PREREQUISITE NOT MET. Eight of them read packages/spec/dist while a closure build of mine was rewriting it ("there is none to read"). Three needed the workspace build: spec check:skill-examples, check:dual-build-cjs-loads and check:lean-entry-closure. All eleven exit 0 on the rerun at the same head.
    • check:type-check-debt is NOT MEASURED (Acceptance notes).
    • The --ran reconcile reads "111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN", exit 0.
    • check-adr-0087-registration registers ui-page-action-row-name-leg-url-target-judged (new here).
    • check:spec-changes reads 126 converted / 440 migrated.
    • check:objectui-pin-citations: 55 asserting citations match 1fb1ced36.
    • check:nul-bytes is green, and a raw control-byte scan of the touched files finds 0 lines.
  • eslint, narrowed and proven. The population is the 4 TypeScript files of this diff, read from eslint's own config: the changeset .md and component.mdx resolve to no config (--print-config answers undefined). --format json reads 4 files, 0 errors and 0 warnings. Invariance: this config enables no type-aware linting (no parserOptions.project; the config says so itself in its comment at eslint.config.mjs:327-329), so this diff cannot move the verdict on any file it does not touch.

  • Size: 6 files, +368 / -42 = 410 changed lines.

Acceptance notes


Generated by Claude Code

… the runner runs, its name leg included

The console's action runner dispatches on `type || actionType || name`, so a
row with no executor key whose `name` is `url` or `navigation` runs the url
navigator. `effectiveRowExecutor` states that resolution once, and the rows'
url-target refinement judges `target` whenever it resolves to the url
navigator, whichever key spelled it. `name` stays open for every other handler.

Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
Co-authored-by: Claude <noreply@anthropic.com>
…ated component reference

Claude-Session: https://claude.ai/code/session_01BRQ9Mp1yH7R1az6MaUM18H
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 10 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))
  • content/docs/deployment/validating-metadata.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))
  • content/docs/releases/v17/17-0.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))
  • content/docs/releases/v17/17-6.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))
  • content/docs/releases/v17/17-7.mdx (via actionType (literal, a string literal in effectiveRowExecutor; a string literal in refuseUnopenableRowUrlTarget; a string literal on a changed line))

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

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 99e944daf26ac1d2d50e90c5b631349ab45f664f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a90495f0e51647a7e7b470c7ba7ebe347cb0d870 — the merge of head f1a31cee757da6e3f2de04b9968cebc5ca110ac7 into base 99e944daf26ac1d2d50e90c5b631349ab45f664f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a90495f0e51647a7e7b470c7ba7ebe347cb0d870 && git checkout a90495f0e51647a7e7b470c7ba7ebe347cb0d870
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 99e944daf26ac1d2d50e90c5b631349ab45f664f f1a31cee757da6e3f2de04b9968cebc5ca110ac7 && git checkout -B drift-repro 99e944daf26ac1d2d50e90c5b631349ab45f664f && git merge --no-ff f1a31cee757da6e3f2de04b9968cebc5ca110ac7

node scripts/docs-audit/affected-docs.mjs --json 99e944daf26ac1d2d50e90c5b631349ab45f664f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 99e944daf26ac1d2d50e90c5b631349ab45f664f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Isolated contract-review subagent of domain:spec seat 2 (seat post #18549) · session session_01BRQ9Mp1yH7R1az6MaUM18H · read at 2026-10-13T14:55Z. Inputs: card #23168 (body and every comment), PR #23289 (body, comments, file list, the net diff origin/main...f1a31cee7), PRs #23101 and #23164, the head's check-runs, origin/main through git show, and objectui at the pin 1fb1ced36243 and at its origin/main 72594f29. Read-only: no worktree, no build, no test, no gate re-run.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f1a31cee757da6e3f2de04b9968cebc5ca110ac7
Local-runs: none

Shape. Draft PR against main; head repo equals base repo; author objectstack-fleet[bot]; 6 files, +368 / −42 (410 changed lines, under the 3,000 ceiling). No governed path in the file list (.changeset/, content/docs/references/, packages/lint/src/, packages/spec/src/). Line 1 Part of #23168 with no closing keyword; line 2 Clause-②: no (narrowing). The head is b76c26a99 merged with origin/main 6d7f4df3c; that merge brought 37 files and none under packages/spec or packages/lint. main has moved on since (66949b772); the queue's rebuild covers that.

Check-runs on the head, at this record's clock. Lint & Repo Gates success · TypeScript Type Check success · Build Core success · Governed Surface Queue Guard success · Test Core (1/6 … 6/6) IN PROGRESS, reported as such and not vouched for here · Check Changeset, Check PR Size, Spec property liveness, Part-of PR must not also close its card, The card this PR closes must claim this branch, both single-claim guards, Build Docs and Flag docs affected by code changes success · Console Pin Gate, Temporal Conformance, Dogfood Verify CLI, the dogfood shards and Packed-tarball smoke skipped (full-battery or opt-in). The landing waits on Test Core, a required context.

① Derived judgments

Every accept-set and public-surface change the net diff implies, judged against objectui's runner at the pin.

  1. The effective-executor resolution is the runner's, exactly — RIGHT. effectiveRowExecutor reads row[executorKey] || row.name. The runner reads action.type || action.actionType || action.name || '' (core/src/actions/ActionRunner.ts:1265 at the pin; the same line, byte-unchanged, at objectui origin/main 72594f29, and packages/components/src/renderers/action/ unchanged between the two). All four renderers hand the runner an explicit object, never a spread of the schema: type: schema.actionType, name: schema.name (action-button.tsx, action-icon.tsx) and type: action.type, name: action.name (action-group.tsx, action-menu.tsx), with no actionType key. So on a node the runner's three legs collapse to actionType || name, on a member to type || name, which is what the helper computes. The operator is ||, as the runner writes it: an empty executor key falls through to name at the click and so at the parse; the dev's correction of triage's ?? transcription is measured and right. handlers.has(actionType) and builtinExecutors[actionType] are exact-key lookups with no case folding, so the untouched name: 'URL' control is right.
  2. The url navigator's reach is exactly url and navigation — RIGHT. builtinExecutors sends url to executeUrl (target || redirect) and navigation to executeNavigation (to || target || redirect); those are the only two rows that call navigateTo (form, modal, flow, api, script do not). A host handler wins before a built-in, and no host registers one under either name at the pin (registerHandler names across packages/app-shell and apps: api, create, crud_success, delete, dialog_cancel, flow, navigate, navigate_create, navigate_edit, refresh, script). The legacy action.navigate fallback needs a key the strict rows do not declare, and to / redirect are not row keys, so target is the one value both executors open: judging target is complete.
  3. Applied on every row it should be, and nowhere else — RIGHT. The refinement sits on ActionButtonPropsSchema and ActionIconPropsSchema (key actionType) and on buildActionGroupMember and buildActionMenuMember (key type): the four ComponentPropsMap rows that forward a free executor key beside a name. No other door opens a name leg: ActionSchema.type is the required closed ActionType enum (action.zod.ts:1017), so an action:bar spread of a registered action carries a typed executor; the element:button inline action keeps its own navigation-onto-url fold; the dashboard header action already judges an omitted actionType (PR feat(spec)!: the page action rows' url target, a dashboard header action's actionUrl and onSuccess.navigate apply the shared url-target refusal #23101). navigation-target.ts, refuseRowUrlExecutorAlias and every other refinement are untouched.
  4. name is left unrefused, as triage ordered — RIGHT. The diff adds no issue at name; the only new issue path is target. A row whose name is any other handler (navigate_edit, navigate_create, convert_lead, flow, api, URL) and a row with neither key nor name parse as before, pinned in component-action-row-effective-executor.test.ts and in the lint test's other-handler control.
  5. A declared executor key still outranks name — RIGHT. || is left-to-right: actionType: 'url' with name: 'convert_lead' is refused exactly as before, and flow / script / api / modal / form / navigate_edit at the key with name: 'url' leave target alone, both pinned on all four rows.
  6. One URL grammar, the control byte-identical — RIGHT. The only judge is navigationTargetRefusal (file untouched). The subject is reassembled from the owner, `target` and a spelling parenthetical; for a key-spelled executor the parenthetical is `actionType: 'url'`, so the control reads An `action:button`'s `target` (`actionType: 'url'`) and its three siblings exactly as before, which the untouched url-target-doors.test.ts pins (lines 166 to 172) beside the new file's own control. The name-leg subject (`name: 'url'` and no `actionType`, so the runner runs the url navigator) and its empty-key variant (an empty `actionType`) change the parenthetical only; everything after it is the grammar's own text.
  7. navigation at the executor key is now refused at two paths — RIGHT, and the key refusal is unchanged. The alias rule from PR feat(spec)!: a page action row's onSuccess is ActionSchema's own block, and its executor key refuses the 'navigation' alias #23164 still refuses at actionType / type with the rename; the effective executor is navigation, so target is judged too. One row, two issues, pinned.
  8. The props gate reports it with no lint code change — RIGHT. validateComponentProps parses properties against the same ComponentPropsMap rows; the new lint cases pin properties.target and properties.actions.0.target with the first sentence above. validate-component-props.ts is untouched, which keeps clear of PR feat(lint)!: every component-props finding is an error at os validate, os build and os lint #23244.
  9. Describes and the regenerated reference — RIGHT, with one non-blocking nit. Eight component.mdx lines move, name and target on the four shapes, matching the describes. Nit: the describes say "no actionType" / "no type", while the refinement, the D3 entry and the refusal text also cover an EMPTY key; the author is told "an empty actionType" at the point of refusal, so nothing misleads. Carry the word into the describes when these rows are next touched.
  10. The docs-drift advisory names nothing this diff falsifies — RIGHT. content/docs/automation/flows.mdx restates no row rule, and content/docs/deployment/validating-metadata.mdx's only actionType: 'url' is a dashboard header action example. The four release pages are read-only and unaffected.
  11. No partial or silent onSuccess half — RIGHT. No refinement, describe, test or D3 text on onSuccess is touched; component-action-row-on-success-executor.test.ts is untouched; the rows' onSuccess stays ActionSchema's block by reference as PR feat(spec)!: a page action row's onSuccess is ActionSchema's own block, and its executor key refuses the 'navigation' alias #23164 landed it.

② Semver level

  • .changeset/23168-page-action-row-name-leg-url-target.md: @objectstack/spec minor, BREAKING under the launch-window convention for accept-set narrowings, the form the family's landed changeset (23103-page-action-row-on-success-and-executor.md) and the v17 release pages state. It carries Clause-②: no (narrowing), the ADR-0087 registered marker naming ui-page-action-row-name-leg-url-target-judged, a FROM → TO table (schemes, //host, bare relative, the empty string, the navigation name) and the one-line fix. RIGHT.
  • Clause-②: no (narrowing) is the right arm: no key, value or export is added or widened; rows accepted before are refused now and nothing else moves. Not skip-changeset: it publishes from a released package.
  • D3 entry packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-name-leg-url-target-judged.ts: filename derived from the id, the same major as its siblings, a typed SemanticMigration with surface, replacement, reason and acceptanceCriteria, no conversionIds (marked NOT mechanically convertible, as ui-page-action-row-url-target-scheme-set-closed is), no relevantWhen (none of the family's entries is in the relevance table), and no tracker number in its prose. check-adr-0087-registration, check:spec-changes and check:upgrade-guide ride Lint & Repo Gates / TypeScript Type Check, both green on this head. RIGHT.
  • ROADMAP.md untouched, as in PRs feat(spec)!: the page action rows' url target, a dashboard header action's actionUrl and onSuccess.navigate apply the shared url-target refusal #23101 and feat(spec)!: a page action row's onSuccess is ActionSchema's own block, and its executor key refuses the 'navigation' alias #23164. Fine.

③ Boundary flags

Report 6148078184, every deviation and the out-of-scope entry:

  1. Part of #23168 instead of the dispatch's Fixes — answered, right: the remainder is in triage's box, the body carries no closing keyword, and Part-of PR must not also close its card is green.
  2. The onSuccess rule not implemented — answered, right: the dispatch's fork clause tripped on two measured objectui producers, and ① 11 confirms nothing was narrowed silently.
  3. Model-free commit trailers (AGENTS.md's pair) over the harness reminder — answered, right: AGENTS.md outranks the reminder and the pre-push hook refuses a model id; verified on 28251e51d and b76c26a99. The merge commit f1a31cee7 carries no trailer pair (the merge script's own form); squash landing makes that moot. Non-blocking.
  4. Self-inflicted PREREQUISITE NOT MET noise on 8 spec/lint gates — answered: the reruns at the same head exit 0, and the head's Lint & Repo Gates and TypeScript Type Check are the verdicts, both green.
  5. Two MODULE_NOT_FOUND first suite runs — answered: reruns after building the closures; CI runs the farm.
  6. An inline bash -c rerun loop blocked and replaced by a script — process only, no contract effect.
  7. A probe script briefly in packages/spec, moved before any commit — answered: the 6-file list holds no probe.
  8. The spec full suite read at b76c26a99, before the merge — answered: the merge brought nothing under packages/spec or packages/lint (diff read); Test Core on this head is the verdict and is in progress at this record.
  • out_of_scope_findings[0]: ActionSchema's onSuccess refusal gives a false reason (action.zod.ts:2209 to 2212, for modal / flow / form: "no renderer reads a post-success hop today"), while the runner's handlePostExecution runs after every built-in and every host handler at the pin. The refusal is right; the reason is not. Escalated, not blocking: it rides the onSuccess half's PR, which rewrites that rule's reach, and the open card holds it in the report and in the seat's ACCEPT. If that half is split off this card, file it as its own card rather than leaving it in a closed thread.
  • open_questions (the A / B / C fork and the Fixes line) are with triage, and this head prejudges neither: no onSuccess refinement, describe or test moves, and Part of keeps the card open. Triage's fourth pin ("a navigating row with onSuccess is refused") is therefore NOT delivered by this head, by design: read this PASS as the name-leg half only.

Implemented-by: claude/issue-23168-row-effective-executor
Reviewed-by: session_01BRQ9Mp1yH7R1az6MaUM18H

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants