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
spec: a page action row's onSuccess is z.unknown(), so onSuccess.navigate: 'javascript:…' on an action:button / action:icon / action:group or action:menu member parses and validates clean — the fourth door of ruling A+'s url-target family #23103
Filing gate ①, class (b). reach: the public authoring door. It was measured through @objectstack/lintvalidateComponentProps (the os validate props gate) at PR #23101's head ed59283ff.
Filed by domain:spec seat 1 (#6017) · os-warren · session session_019G5ZnRrTMiwcHG6iZM19zf, from the dev report on #23077 (PR #23101, out-of-scope finding 3). ⛔ Not a claim. Lane and grade are triage's.
What is measured
An action:button with actionType: 'api' and onSuccess: { navigate: 'javascript:alert(1)' } answers 0 findings at the props gate.
ActionButtonPropsSchema parses it clean, and so does an action:group member.
The page action rows declare onSuccess: z.unknown().optional() ("Declared post-success navigation, forwarded to the runner"). This covers ActionButtonPropsSchema, ActionIconPropsSchema and actionContainerMemberShape() in packages/spec/src/ui/component.zod.ts.
The rows' onSuccess gets a declared shape that judges navigate with navigationTargetRefusal. That could mean reusing the action's own onSuccess shape, or another declared shape. This is a contract choice for the claim, not a mechanical in-place fix. ⛔ No second URL grammar.
Runtime side. objectui's runner treats 'navigation' as an alias of url: executeNavigation → navigateTo → classifyNavigationTarget, at the pin 7578323a544e (lines 1150, 2221 and 1964, as the review read them).
The result.actionType: 'navigation' with target: 'javascript:…' parses clean, and the click refuses it.
Producers. None known in objectstack or objectui.
For this card's claim: close the alias in the same contract choice as onSuccess. The rows can judge target on every executor value the runner reads as a url, or the executor key can be closed to the runner's vocabulary so 'navigation' is refused or normalised at parse. ⛔ No second URL grammar.
Triage: first grade, bug · security · priority:p1 · domain:spec · area:access, into pm:queue. The fourth door of ruling A+'s family, with the folded actionType position. It runs after #23077
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-12T22:51Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read: 6133636513.
Claim: PM loop round 1 (#23103, queue-jumped on the maintainer's word: a page action row's onSuccess takes ActionSchema's own onSuccess shape, and its executor key takes the declared action-type set, so the fourth door of ruling A+'s family and the folded 'navigation' alias both judge through navigationTargetRefusal) · 2026-10-13T02:02Z
Session: session_019G5ZnRrTMiwcHG6iZM19zf
Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee from this act)
Branch: claude/issue-23103-row-onsuccess
Worktree: objectstack-issue-23103
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
Ruling-ref: ruling A+ on objectstack-ai/objectui#12191; triage direction 6134148885. Queue jump: the maintainer's word in this seat's chat, verbatim 「23103 插队」.
File surface (on origin/main once PR #23101 is merged; stop on breach and explain in the report):
packages/spec/src/ui/component.zod.ts: the onSuccess and executor keys of ActionButtonPropsSchema, ActionIconPropsSchema and actionContainerMemberShape().
packages/spec/src/ui/action.zod.ts: only to export or share ActionSchema's onSuccess shape, with no change to what it accepts.
packages/lint: tests only, for the props gate.
packages/spec/dropped-refinements.baseline.json as build-schemas re-derives it, the ADR-0087 D3 entry or entries, the generated references check:generated names, and one .changeset/23103-*.md.
⛔ No second shape and no second URL grammar.
Container & model: S to M (two keys on four row shapes, with pins through the parse and the props gate), mode:subagent, model: default tier (no path-derived mandate; Clause-② suspect on packages/spec/src/**). It owes a review at CONTRACT_REVIEW_TIER before the queue.
Clause-②: no (narrowing: z.unknown() and a free string narrow to what the contract states)
Responsibility: the contract admits on the rows a target objectui's runner refuses at the click | none at the contract: PR #23101 closed three doors, not these two keys | any author or AI writing a page action row; the runner's own refusal holds today
Thread-read: 6134148885
Serial constraints cleared (read at 2026-10-13T02:01Z):
os-dev-report
{
"issue": 23103,
"status": "done",
"branch": "claude/issue-23103-row-onsuccess",
"pr": "#23164",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"summary": "The four page action row shapes (action:button, action:icon, and each action:group / action:menu member) now take ActionSchema.onSuccess's own block, by reference. The accessor actionOnSuccessBlock returns ActionSchema.in.shape.onSuccess.unwrap(), the same instance, read off the action's pipe the way buildObjectFormSectionFieldEntry reads FormFieldSchema. So navigate is judged by navigationTargetRefusal through refuseUnopenableOnSuccessNavigate, and a row's verdict on any onSuccess value equals the action's. action.zod.ts is unchanged and no export is added. The executor key (actionType on a node, type on a member) refuses 'navigation', objectui's runner-local alias of 'url', at that key, with the rename onto 'url'. Every other value stays open. A1 held: the runner reads only navigate and openIn from a row's onSuccess (readOnSuccessNavigation, then navigateOnSuccess, at pin 1fb1ced36243), so there is no fork. A2 was partly falsified: ACTION_TARGET_ALIASES and ACTION_NODE_ALIASES alias KEYS, not executor values. The runner's built-in table is the spec's six plus 'navigation', which is its only url-like alias, and on a row the alias runs the same navigateTo on the same target. Closing the key to ActionType would refuse real producers: host handlers navigate_create / navigate_edit / create / delete / navigate / refresh / crud_success / dialog_cancel, this package's released CHANGELOG prescription actionType: 'navigate_edit', and a spec pin test. So the key stays open and only the alias is refused, which all four axes favour (zero producers, lossless rename, one spelling, retire at once). A3 held: os validate's props gate reports both refusals with no lint code change. Census: no row in objectstack or objectui writes an onSuccess outside the block or the executor 'navigation'. The declared rider and its addendum landed TSDoc and describe only (see deviations). Labels: zero writes; the dispatch named none and skip-changeset does not apply. A contract-tier review is owed before the queue. Cleanup (node_modules, then git worktree remove) is the step right after this report.",
"tests": "At bb41814 (the branch merged with origin/main d206d08 through os-regen-merge.sh), every heavy run through os-verify-lock, read from its VERDICT line. Builds: spec build exit 0; workspace build (turbo run build --filter=!@objectstack/docs --concurrency=2) 74/74, exit 0; spec check:generated 'All 14 generated artifacts are up to date', exit 0 (it named only content/docs/references/** before gen:docs). Ledger: build-schemas refused exactly 4 new sites and printed the corrected entries; those 4 are the whole ledger diff (232/752 to 232/756). Typecheck: spec typecheck (tsc, scripts, test layer) and lint typecheck, exit 0. Tests: spec full (vitest run --project local --maxWorkers=2) 661 files, 20046 passed, 1 todo, exit 0; the new component-action-row-on-success-executor.test.ts holds 25 tests. Lint full 139 files, 6670 passed, exit 0; the new validate-component-props-row-on-success.test.ts holds 16 tests. platform-objects full 70 files, 1096 passed. Narrowed and declared: turbo ls --affected names 82 packages (graph-derived, so blind to tests that read another package's source from disk). Beyond the three full suites, I ran every other test file that names an action row, ComponentPropsMap or onSuccess, 9 files in 6 packages, all green: example-showcase 3/14, client-react 1/13, cloud-connection 1/16, create-objectstack 1/37, mcp 1/11, runtime 1/14. The blind spot is a computed row; CI runs the full farm. Ablations, each through scripts/ablation-replace.mjs with its restore trap; each restore ended at blob == HEAD fd59b06cce9e with an empty git diff HEAD. Each mutation leg rebuilt the spec JS dist with tsup (gen:schema would refuse the moved ledger sites under ablation), and ablation-dist-preflight found the marker in 6 dist files. (A) onSuccess accessor set to z.unknown() with marker ablated-23103-onsuccess: spec src leg 13 failed / 12 passed (every onSuccess refusal, control, verdict-parity and identity pin red; executor pins green); lint dist leg 4 failed / 12 passed (the base answers 0 findings at onSuccess.navigate). (B) alias guard compared to ablated-23103-alias: spec 4 failed / 21 passed; lint 4 failed / 12 passed. Restore leg: full spec build, both markers absent from all 232 dist files, tree clean, spec 94/94, lint 16/16. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 111 at bb41814; 110 ran, every exit recorded before any pipe, all exit 0. Six first exited 3 on PREREQUISITE NOT MET (lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure) and exited 0 after the workspace build at the same head. check:type-check-debt is NOT MEASURED, by order. --ran reconcile: '111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN'. check-adr-0087-registration registers both D3 ids. check:objectui-pin-citations: 55 asserting citations match 1fb1ced36. check:nul-bytes is green, and the raw control-byte scan of the 9 touched files finds 0 lines. Size: 9 files, +528/-37 = 565.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — both through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch; each is one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft #23164) + POST /repos//issues/23164/assignees (os-warren); body read back 14185/14185 bytes identical. (2) this os-dev-report comment: POST /repos//issues/23103/comments. Plus git pushes of the branch (not REST). Zero label writes.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: public door — measured at bb41814: os validate's props gate (@objectstack/lint validateComponentProps) answers 0 findings for an action:button with { name: 'url', target: 'javascript:alert(1)' } and no actionType, and the same for name: 'navigation'; the actionType: 'url' control answers 1 finding at properties.target. The runtime half was read from source, not executed · evidence: objectui ActionRunner.execute reads the executor as action.type || action.actionType || action.name (core/src/actions/ActionRunner.ts:1265 at pin 1fb1ced36243). action-button.tsx:286 forwards type: schema.actionType, and its own comment says an omitted actionType 'falls through to its action.name leg'. So a row with no executor key and name 'url' or 'navigation' reaches the url navigator, and its target is judged only at the click (classifyNavigationTarget). This is the ruling A+ family shape: the contract admits what the runtime refuses. Report-named for the family's closing card, not a single-point card. Seam: spec:ActionButtonPropsSchema.name / ActionIconPropsSchema.name / actionContainerMemberShape().name (with the executor key absent) → runtime: objectui ActionRunner.execute name leg · dedupe words: page action row name url executor; actionType omitted name leg; runner dispatches on action name; row target unjudged name navigation",
"carrier: 承接者:无 · noted, not filed — a page action row whose executor is 'url' (or a host handler) with an onSuccess navigates twice; the runner hops after every executor, and ActionSchema's own 'onSuccess only beside api/script' rule is a refinement of the action, not the block, so it does not carry over. Read from source, not executed. In the PR's Acceptance notes and the accessor's TSDoc.",
"carrier: 承接者:无 · noted, not filed — objectui useObjectActions' host 'navigate' handler reads params.url with its own prefix rule (startsWith('/'), else under the app base): a host handler outside the contract's executor set, read only, no producer found. In Acceptance notes."
],
"deviations": [
"Rider, declared on the seat's order: #23036's component.zod.ts TSDoc twin (6137432578), plus the addendum from #23146's contract review (6137629159, ③). The page:tabs item visibleWhen TSDoc now says the component-node evaluator mounts no data root, and its cite moved to containers.tsx:642-649 at 1fb1ced36243 (verified at the pin). The .describe() parenthetical '(not the data-source adapter)' became '(the component-node evaluator mounts no data root)', and component.mdx was regenerated by gen:docs. #23146 had not landed by the last push, so no TSDoc-face assertion was added to expression-scope-data-root.pin.test.ts.",
"Files beyond the claim's literal list, declared in the PR: packages/spec/src/ui/component-props-unknown-members.pin.test.ts (its runner ledger's onSuccess line, now a stale line its §1 second half refuses) and the new spec test file.",
"Base: the empty branch was fast-forwarded to origin/main f931e76 (#23101's merge) before any edit, so BASE = f931e76. A later main (d206d08) came in through scripts/pm/os-regen-merge.sh (merge commit bb41814; no os-regen path conflicted).",
"Process: one full-spec run was first spelled 'pnpm --filter @objectstack/spec test -- --maxWorkers=2' (a bare -- that vitest would drop). I stopped it (my own PID) while it was still queued, before it acquired the lock; nothing was measured, and it was re-run correctly. A props-gate probe script was briefly copied into packages/lint to resolve @objectstack/lint, run once and deleted; git status was clean after."
]
}
Read in the diff (merge base d206d08ca, 9 files, +528 / −37):
The rows' onSuccess takes ActionSchema's own block, by reference.
actionOnSuccessBlock returns ActionSchema.in.shape.onSuccess.unwrap(), the same instance, read off the action's pipe the way buildObjectFormSectionFieldEntry reads FormFieldSchema. That covers action:button, action:icon and every action:group / action:menu member.
navigate is judged by navigationTargetRefusal through refuseUnopenableOnSuccessNavigate, and a row's verdict equals the action's.
action.zod.ts is unchanged, and no export is added: no second shape and no second grammar, as triage directed.
The executor key refuses 'navigation' (refuseRowUrlExecutorAlias), objectui's runner-local alias of 'url', at that key, with the lossless rename onto 'url'.
Two D3 entries, the ledger's 4 re-derived sites, and the regenerated component.mdx.
A2 partly falsified, accepted.ACTION_TARGET_ALIASES and ACTION_NODE_ALIASES alias keys, not executor values. Closing the executor key to ActionType would refuse real producers: host handlers (navigate_create, navigate_edit, …), this package's own released CHANGELOG prescription and a spec pin. So the key stays open and only the url-like alias is refused. That is the narrower reading of triage's "the declared action-type set", argued on the four axes: zero producers of the alias, a lossless rename, one spelling, retired at once. The contract reviewer judges it against the triage text.
The declared rider (#23036's component.zod.ts twin, 6137432578, plus the :1017 addendum from 6137629159): the page:tabs item TSDoc and the describe's parenthetical now say the component-node evaluator mounts no data root, and the cite moved to containers.tsx:642-649 at 1fb1ced36243. #23146 had not landed by the last push, so no TSDoc-face assertion was added. Accepted.
Checklist:
Body: first line Fixes #23103, second line Clause-②: no (narrowing). The changeset is @objectstack/spec minor, BREAKING under the launch-window convention, with FROM → TO.
Scope: inside the claim, plus component-props-unknown-members.pin.test.ts (a stale ledger line) and the new test file, both declared. No governed path, no content/docs/releases/.
objectui's host navigate handler has its own prefix rule: Acceptance notes. It is a host handler outside the contract, with no producer.
CI at this read: see the check-runs. A contract-tier review is owed.
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:dispatched with its assignee until MERGED.
os-dev-report
{
"issue": 23103,
"status": "done",
"branch": "claude/issue-23103-row-onsuccess",
"pr": "#23164",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"round": "fix round 1 on contract review 6138862349 (FAIL) and seat note 6138658212; claim 6136510921",
"head": "f4d39d7aa5fba4817493b617f66103f385df8190 (was bb41814)",
"summary": "All three owed items landed, and the open executor key, the alias refusal and the block-by-reference stand exactly as reviewed. (a) A new D2 conversion, page-action-row-navigation-executor-to-url, sits in MAJOR_18_CONVERSIONS. It is inserted where its identifier sorts (between objectTenancyOrganizationFieldRemoved and pageAssignedProfilesRemoved), at order 63, and defined directly above pageAssignedProfilesRemoved. It has retiredFromLoadPath: true and retiredAfter: '17.7.0', which is both the label when it was written and, under the 18.0.0-next.0 label main now carries, the newest stable release; retired-after.census.test.ts is green. Through mapPageComponents it renames the value 'navigation' to 'url' on action:button / action:icon properties.actionType and on action:group / action:menu properties.actions[].type. It is copy-on-write (untouched members keep their identity), idempotent by construction, and has a disjoint fixture pair (5 notices: button, icon, group member, nested menu member, slotted header icon). Every other executor value, an element:button inline action, a non-object member and a non-array actions are left alone. Its own test file pins the stored-row seam on all four shapes, the parse after the rewrite, the controls, the same reference back, idempotence, and that the authoring funnel does not replay it. The D3 entry ui-page-action-row-navigation-executor-refused now carries conversionIds naming it, and its reason no longer skips the conversion on a census. The changeset's FROM → TO rows and its one-line fix name os migrate meta --write as the rewrite; the onSuccess leg stays by hand. Step 18's rationale gains one fragment, sorted by id, at order 95. (b) refuseUnopenableRowUrlTarget's docblock now guarantees only rows whose EXECUTOR KEY names the url navigator. It states that a row with no executor key dispatches on name, which is #23168's question. refuseRowUrlExecutorAlias's docblock names the conversion. (c) #23146 landed (02bec6c), so expression-scope-data-root.pin.test.ts gains one TSDoc-face assertion for the page:tabs twin: it says 'the component-node evaluator mounts no data root' and carries neither retired claim shape. Turso: the CI red does not reproduce. driver-turso's whole suite is green at bb41814, the failing head (88 files, 2373 passed, 33 skipped), and again at de990e9, 8ec1d54 and f4d39d7 with the same counts. The failing case cannot be named from here: the job log's blob host is outside this container's proxy (GET .../actions/jobs/115135839279/logs was refused), and the check-run annotations carry only 'driver-turso pnpm run test exited (1)'. The diff reaches nothing in driver-turso, so per the order the seat counts this round's push as the one re-run. Merges: main came in twice through os-regen-merge.sh. The first, at e540e7f, conflicted only on the ledger header; I resolved it semantically to main's 234 schemas plus this branch's 4 sites, 759, and build-schemas agrees. The second, at d889448, was clean; it includes the Version Packages (next) landing, which moved the label to 18.0.0-next.0. main then moved 3 more commits (107853c, c6139f2, 44e2d50), none touching these 13 files, and the PR reads mergeable_state clean. A PR body change is owed. The exact text is in pr_body_edits, for the seat to write.",
"items_from_to": [
"② alias leg: FROM a D3 entry with 'No conversion is registered: the authored census finds no page action row writing navigation' and a changeset saying 'the rewrite is by hand'. TO the D2 conversion page-action-row-navigation-executor-to-url (retiredFromLoadPath, retiredAfter '17.7.0', order 63); the D3 entry with conversionIds: ['page-action-row-navigation-executor-to-url'] and a reason that names the conversion as the rewrite; and changeset rows and a one-line fix that name os migrate meta --from 17 --write as the rewrite for the executor, while the onSuccess leg stays by hand.",
"① item 8: FROM 'so no row reaches the url navigator with its target unjudged'. TO 'so no row whose EXECUTOR KEY names the url navigator reaches it with its target unjudged. A row with no executor key at all is outside both rules: the runner then dispatches on the row's name, so name: 'url' reaches the same navigator with target judged only at the click — that leg is #23168's question, not settled here.'",
"① item 5 (optional): FROM no TSDoc-face assertion. TO one test in expression-scope-data-root.pin.test.ts. It reads component.zod.ts between the 'Conditional tab (CEL, #2606)' anchor and the tab item's visibleWhen declaration, and asserts that the text contains 'the component-node evaluator mounts no data root' and carries neither RETIRED_CLAIM_SHAPES entry nor 'binds it to the data-source ADAPTER'."
],
"turso_finding": "NOT REPRODUCED. driver-turso's full suite (pnpm exec vitest run --maxWorkers=2 in packages/drivers/driver-turso, after building its dependency closure) ran at bb41814, the failing CI head: 88 files, 2373 passed, 33 skipped, exit 0 (VERDICT command-exit 0, held 119s). It ran identically at de990e9, 8ec1d54 and f4d39d7. The failing case is not nameable from this container: the job log's storage host (productionresultssa0.blob.core.windows.net) is outside the proxy, and the check-run annotations for job 115135839279 say only 'command (packages/drivers/driver-turso) pnpm run test exited (1)'. No interaction with this diff was found: driver-turso imports @objectstack/spec/data, /contracts and /api only, and nothing in it names an action row, ComponentPropsMap or onSuccess. Reading: a flake in that shard, with the case unnamed. The seat's #17079 timing-window record is the likely signature, but that is not verified here.",
"tests": "Final head f4d39d7. Every heavy run went through os-verify-lock and is read from its VERDICT line. The final verification batch (workspace build, five targeted spec files, spec full, lint full, the lint row pin, driver-turso): VERDICT command-exit 0, held 1368s. Inside it: workspace build 74/74; targeted spec 5 files / 313 passed (page-action-row-navigation-executor-to-url.test.ts, conversions.test.ts, retired-after.census.test.ts, component-action-row-on-success-executor.test.ts, expression-scope-data-root.pin.test.ts); spec full 663 files, 20149 passed, 1 todo; lint full 138 files, 6680 passed; the lint row pin 16/16; driver-turso 88 files, 2373 passed, 33 skipped. Typecheck: spec typecheck and lint typecheck, VERDICT command-exit 0. check:generated: 'All 14 generated artifacts are up to date', exit 0. Intermediate head de990e9: build plus 12 targeted files (including conversions-major18-merge.test.ts, step18-rationale-merge.test.ts and src/migrations) read 610 passed; spec full 663 / 20135; lint full 138 / 6636; driver-turso 2373 passed; all VERDICT command-exit 0. Ablations, src-only, through scripts/ablation-replace.mjs at de990e9. (C) node leg, guard compared to ablated-23103-conv-node: 3 failed / 261 passed (the fixture pair and both node stored-row pins). (D) member leg, container set emptied to ablated-23103-conv-member: 3 failed / 261 passed (the fixture pair and both member pins). Each was restored to blob 5795af427533 == HEAD with an empty git diff HEAD, and the tree was clean after. Round 1's A/B ablations stand. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 111 at f4d39d7; 110 ran, every exit recorded before any pipe, all exit 0 (the workspace was built first, so no prerequisite exit 3 this time). check:type-check-debt is NOT MEASURED, by order. --ran: '111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN'. check-adr-0087-registration registers both D3 ids. check:spec-changes reads 125 converted / 436 migrated (was 124). check:nul-bytes is green, and the raw control-byte scan of the 13 touched files finds 0 lines. Size: 13 files, +899/-37 = 936.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "1 this round — this os-dev-report comment, through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos//issues/23103/comments. Plus git pushes of the branch (de990e9, 90dae0f, 8ec1d54, f4d39d7; all fast-forward, not REST). No PR body write: the edits go to the seat as exact text.",
"pr_body_edits": [
{
"find": "| actionType on action:button / action:icon | any string | any string except 'navigation', which is refused at actionType with the rename onto 'url' |",
"replace": "| actionType on action:button / action:icon | any string | any string except 'navigation', which is refused at actionType with the rename onto 'url'; os migrate meta --write and the stored-row seam rewrite it (D2 page-action-row-navigation-executor-to-url) |"
},
{
"find": "| type on an action:group / action:menu member | any string | any string except 'navigation', which is refused at type with the rename onto 'url' |",
"replace": "| type on an action:group / action:menu member | any string | any string except 'navigation', which is refused at type with the rename onto 'url'; os migrate meta --write and the stored-row seam rewrite it (D2 page-action-row-navigation-executor-to-url) |"
},
{
"find": "No D2 conversion is registered, because no producer exists.",
"replace": "The executor leg is lossless, so it also lands as the D2 conversion page-action-row-navigation-executor-to-url: retiredFromLoadPath: true, retiredAfter: '17.7.0', in MAJOR_18_CONVERSIONS at order 63. The D3 entry names it in conversionIds. The onSuccess leg stays D3 only, because a refused address has no lossless rewrite."
},
{
"find": " - #23146 had not landed at the last push, so no TSDoc-face assertion was added to its expression-scope-data-root.pin.test.ts.",
"replace": " - #23146 landed (02bec6ca86), and expression-scope-data-root.pin.test.ts gains the TSDoc-face assertion for this page:tabs twin."
},
{
"find": " - The regenerated component.mdx, and two D3 entries.",
"replace": " - The regenerated component.mdx, and two D3 entries.\n - From the fix round on contract review 6138862349: the D2 entry in conversions/registry.ts, its test page-action-row-navigation-executor-to-url.test.ts, and its step-18 rationale fragment in migrations/registry.ts.template."
},
{
"find": "This is the same family, reported in the dev report for the seat to place; it is not fixed here.",
"replace": "This is the same family, carried by #23168, and it is not fixed here. refuseUnopenableRowUrlTarget's docblock now scopes its guarantee to the executor key."
},
{
"section": "## Verification record",
"replace_whole_section_body_with": "All readings are at f4d39d7aa unless stated. That head is this branch merged with origin/maind889448b9 through scripts/pm/os-regen-merge.sh; three later main commits touch none of these 13 files. Every heavy run went through scripts/pm/os-verify-lock.sh, and each result is its VERDICT line.\n\n- Builds:\n - The workspace build: 74 / 74, exit 0.\n - @objectstack/spec check:generated: "All 14 generated artifacts are up to date", exit 0.\n - The ledger moved only by main's own sites: 234 / 759 after the merge, with this branch's four sites kept.\n- Typecheck:@objectstack/spec typecheck and @objectstack/lint typecheck, exit 0.\n- Tests:\n - Spec, full: 663 files, 20149 passed, 1 todo.\n - Lint, full: 138 files, 6680 passed.\n - Five spec files, 313 passed: the conversion's own test, conversions.test.ts, retired-after.census.test.ts, the row pin and expression-scope-data-root.pin.test.ts.\n - The lint row pin: 16 / 16.\n - driver-turso: 88 files, 2373 passed, 33 skipped.\n- Ablations:\n - Round 1's two, (A) the rows' onSuccess block and (B) the alias refusal, stand as recorded at bb41814b8.\n - Round 2 adds two src-only ablations of the conversion, through scripts/ablation-replace.mjs, at de990e9bf. The node leg's guard was compared to ablated-23103-conv-node, and the member leg's container set was emptied.\n - Each read 3 failed / 261 passed over the conversion test plus conversions.test.ts: the fixture pair, and that leg's two stored-row pins.\n - Each was restored to blob == HEAD with an empty git diff HEAD.\n- Gates:dispatch-gates --commands --repo objectstack-ai/objectstack derived 111 at f4d39d7aa.\n - 110 ran, all exit 0.\n - check:type-check-debt is NOT MEASURED, by order.\n - The --ran reconcile reads "111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN".\n - check-adr-0087-registration registers both D3 ids. check:spec-changes reads 125 converted / 436 migrated.\n- Size: 13 files, +899 / -37 = 936 changed lines."
}
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — CI job logs are unreadable from a cloud seat container: GET /repos/{o}/{r}/actions/jobs/{id}/logs redirects to productionresultssa0.blob.core.windows.net, outside the agent proxy, so a dev cannot name a failing CI case from here. That is an environment fact, not a repo defect."
],
"deviations": [
"Files beyond the claim's literal list, owed by the review's ②: packages/spec/src/conversions/registry.ts (the D2 entry), its test page-action-row-navigation-executor-to-url.test.ts, packages/spec/src/migrations/registry.ts.template (one STEP18_RATIONALE fragment, as the spec-property-retirement kit's step-18 item asks) and packages/spec/src/ui/expression-scope-data-root.pin.test.ts (item c).",
"After the second merge (8ec1d54) I first rebuilt without reinstalling; main had moved pnpm-lock.yaml and packages/verify/package.json, so @objectstack/verify's DTS build failed (TS2307 @objectstack/metadata). I then ran pnpm install --frozen-lockfile, and the workspace built 74/74. The spec, lint and turso suites at 8ec1d54 had run before that reinstall; none of them depends on @objectstack/verify. The final head's numbers all come from runs after the reinstall.",
"A local helper that ran gate commands through bash -c with a variable was refused by the harness's removal-safety check (it could not verify the script). I did not retry it; I ran the six gate commands one by one, literally, instead. No GitHub write was involved.",
"main moved three commits after the final merge (107853c, c6139f2, 44e2d50). None touches these 13 files, so I did not merge again; the PR reads mergeable_state clean, and CI's merge ref covers the join."
]
}
Read in the round's own commit, de990e9bf (7 files, +385 / −14; the three later commits are merges of main):
(a) The conversion the review owed:page-action-row-navigation-executor-to-url is in MAJOR_18_CONVERSIONS with retiredFromLoadPath: true and retiredAfter: '17.7.0'.
Through mapPageComponents, it renames 'navigation' to 'url' on properties.actionType of action:button / action:icon, and on each member's type in properties.actions of action:group / action:menu. Nothing else changes.
It is copy-on-write and idempotent. Its fixture pair covers a button, an icon, a group member, a nested menu member and a slotted header icon. Its controls are 'url', a host handler name, an element:button inline action and the non-alias member.
The D3 entry ui-page-action-row-navigation-executor-refused names it in conversionIds, and its reason no longer says "No conversion is registered".
The changeset's FROM → TO rows name os migrate meta --write and the stored-row rewrite. The onSuccess leg stays a by-hand fix: a refused address has no lossless rewrite.
The step-18 rationale gains one fragment, in registry.ts.template; the generated registry.ts is untouched.
What the review kept is unchanged: the open executor key, the alias refusal, and onSuccess as ActionSchema's own block by reference.
Checklist:
Scope: four files beyond the claim's literal list, each owed by the review's ②: the conversion registry, the conversion's test, the template fragment, and the pin test. Accepted as declared.
No content/docs/releases/, no governed path (check-governed-merges: NOT governed).
mcp_calls 0. Writes: the report only. The body edits went to the seat as exact text.
Evidence accepted:
At f4d39d7aa:
spec in full: 663 files / 20,149 tests;
lint in full: 138 / 6,680;
the five targeted spec files: 313;
the lint row pin: 16 / 16.
Typecheck is green and check:generated is clean.
Two new src ablations, one per conversion leg. Each turned exactly the fixture pair and that leg's two stored-row pins red, and was restored.
Gates: 111 derived, 110 run, 0 unrun. check:type-check-debt was not measured, by order.
The driver-turso red at bb41814b8 (Test Core (1/6)): it does not reproduce. The full driver-turso suite is green at that head and at all three later heads, and Test Core (1/6) is green in CI on de990e9bf and later. The diff reaches nothing in driver-turso, which reads @objectstack/spec only through data, contracts and api. The case could not be named, because the job log's storage host is outside this container's proxy. Recorded as an unnamed flake; this round's push was its one re-run.
CI at this read: 37 success, 7 skipped, 4 in progress, 0 failed. mergeable_state reads clean.
Report items, one line each:
The CI job logs are unreadable from a cloud seat container, because their storage host is outside the proxy. That is an environment fact, not a repo defect: noted, not filed.
Then: a new contract-tier review on f4d39d7aa, isolated from this seat; the FAIL 6138862349 judged bb41814b8. On PASS: CI green, the pre-queue checks, ready, and the queue. The card stays pm:dispatched with its assignee until MERGED.
the fix round: report 6141806071, ACCEPT 6141863671, contract review 6142071573 (PASS on f4d39d7aa).
What now holds (the fourth door of ruling A+'s url-target family):
A page action row's onSuccess, on action:button / action:icon and on each action:group / action:menu member, is ActionSchema's own post-success block, by reference. So onSuccess.navigate is judged by the one url grammar, and javascript: and the other refused schemes no longer parse or validate clean.
The row's executor key stays open to host handler names, but refuses 'navigation', the runner's local alias of 'url', with the rename. The D2 conversion page-action-row-navigation-executor-to-url is retired from the load path: it rewrites stored rows and os migrate meta --write, while authors meet the refusal.
refuseUnopenableRowUrlTarget guarantees only rows whose executor key names the url navigator.
Activity
objectstack-fleet commentedon Oct 12, 2026
A second position of the same family, folded here: a page action row's
actionType: 'navigation'domain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-12T22:17Z. ⛔ Not a claim.PR #23101's contract review (
6133623543, ③ item 4) asks for a carrier for this, and it is this card's shape: the rows admit what the click refuses.z.string()(actionTypeonaction:button/action:icon;typeon anaction:group/action:menumember). 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 judgestargetonly when that key reads'url'.'navigation'as an alias ofurl:executeNavigation→navigateTo→classifyNavigationTarget, at the pin7578323a544e(lines 1150, 2221 and 1964, as the review read them).actionType: 'navigation'withtarget: 'javascript:…'parses clean, and the click refuses it.For this card's claim: close the alias in the same contract choice as
onSuccess. The rows can judgetargeton every executor value the runner reads as a url, or the executor key can be closed to the runner's vocabulary so'navigation'is refused or normalised at parse. ⛔ No second URL grammar.objectstack-fleet commentedon Oct 12, 2026
Triage: first grade,
bug·security·priority:p1·domain:spec·area:access, intopm:queue. The fourth door of ruling A+'s family, with the foldedactionTypeposition. It runs after #23077Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-12T22:51Z. ⛔ Not a claim, ⛔ not a dispatch. Thread-read:6133636513.security: this is the same family and the same ruling as [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 (p1). A page action row'sonSuccessand its free executor key admit targets the console's runner refuses. The runner holds today, so this tightens the contract. It is a tightening, so it does not escalate.onSuccessreusesActionSchema's ownonSuccessshape, which already judgesnavigatewithnavigationTargetRefusalsince 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. ⛔ No second shape and no second URL grammar.6133636513): the rows' executor key takes the declared action-type set, so a navigation-type row is judged the way aurlrow is.Clause-②: no: this narrowsz.unknown()and a free string to what the contract states, so it owes the contract-tier review before the queue.os validate's props gate.component.zod.ts. This card sequences after it.packages/spec/src/ui/component.zod.ts(domain:spec); rationale: contract doors.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1 (#23103, queue-jumped on the maintainer's word: a page action row's
onSuccesstakesActionSchema's ownonSuccessshape, and its executor key takes the declared action-type set, so the fourth door of ruling A+'s family and the folded'navigation'alias both judge throughnavigationTargetRefusal) · 2026-10-13T02:02ZSession:
session_019G5ZnRrTMiwcHG6iZM19zfAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee from this act)Branch:
claude/issue-23103-row-onsuccessWorktree:
objectstack-issue-23103Domain:
domain:specSeat:
domain:spec#1(seat post #6017)Ruling-ref: ruling A+ on objectstack-ai/objectui#12191; triage direction
6134148885. Queue jump: the maintainer's word in this seat's chat, verbatim 「23103 插队」.File surface (on
origin/mainonce PR #23101 is merged; stop on breach and explain in the report):packages/spec/src/ui/component.zod.ts: theonSuccessand executor keys ofActionButtonPropsSchema,ActionIconPropsSchemaandactionContainerMemberShape().packages/spec/src/ui/action.zod.ts: only to export or shareActionSchema'sonSuccessshape, with no change to what it accepts.packages/lint: tests only, for the props gate.packages/spec/dropped-refinements.baseline.jsonasbuild-schemasre-derives it, the ADR-0087 D3 entry or entries, the generated referencescheck:generatednames, and one.changeset/23103-*.md.Container & model:
StoM(two keys on four row shapes, with pins through the parse and the props gate),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:
z.unknown()and a free string narrow to what the contract states)Responsibility:
the contract admits on the rows a target objectui's runner refuses at the click | none at the contract: PR #23101 closed three doors, not these two keys | any author or AI writing a page action row; the runner's own refusal holds todayThread-read: 6134148885
Serial constraints cleared (read at 2026-10-13T02:01Z):
component.zod.tsand the same ledger. It is in the merge queue since 2026-10-13T01:57Z. This card editscomponent.zod.ts,action.zod.tsand the ledger only on amainthat carries feat(spec)!: the page action rows' url target, a dashboard header action's actionUrl and onSuccess.navigate apply the shared url-target refusal #23101, merged in throughscripts/pm/os-regen-merge.sh. Measurement may start now.This act moves the card
pm:queue→pm:dispatchedand assignsos-warren.objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23103,
"status": "done",
"branch": "claude/issue-23103-row-onsuccess",
"pr": "#23164",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"summary": "The four page action row shapes (action:button, action:icon, and each action:group / action:menu member) now take ActionSchema.onSuccess's own block, by reference. The accessor actionOnSuccessBlock returns ActionSchema.in.shape.onSuccess.unwrap(), the same instance, read off the action's pipe the way buildObjectFormSectionFieldEntry reads FormFieldSchema. So navigate is judged by navigationTargetRefusal through refuseUnopenableOnSuccessNavigate, and a row's verdict on any onSuccess value equals the action's. action.zod.ts is unchanged and no export is added. The executor key (actionType on a node, type on a member) refuses 'navigation', objectui's runner-local alias of 'url', at that key, with the rename onto 'url'. Every other value stays open. A1 held: the runner reads only navigate and openIn from a row's onSuccess (readOnSuccessNavigation, then navigateOnSuccess, at pin 1fb1ced36243), so there is no fork. A2 was partly falsified: ACTION_TARGET_ALIASES and ACTION_NODE_ALIASES alias KEYS, not executor values. The runner's built-in table is the spec's six plus 'navigation', which is its only url-like alias, and on a row the alias runs the same navigateTo on the same target. Closing the key to ActionType would refuse real producers: host handlers navigate_create / navigate_edit / create / delete / navigate / refresh / crud_success / dialog_cancel, this package's released CHANGELOG prescription actionType: 'navigate_edit', and a spec pin test. So the key stays open and only the alias is refused, which all four axes favour (zero producers, lossless rename, one spelling, retire at once). A3 held: os validate's props gate reports both refusals with no lint code change. Census: no row in objectstack or objectui writes an onSuccess outside the block or the executor 'navigation'. The declared rider and its addendum landed TSDoc and describe only (see deviations). Labels: zero writes; the dispatch named none and skip-changeset does not apply. A contract-tier review is owed before the queue. Cleanup (node_modules, then git worktree remove) is the step right after this report.",
"tests": "At bb41814 (the branch merged with origin/main d206d08 through os-regen-merge.sh), every heavy run through os-verify-lock, read from its VERDICT line. Builds: spec build exit 0; workspace build (turbo run build --filter=!@objectstack/docs --concurrency=2) 74/74, exit 0; spec check:generated 'All 14 generated artifacts are up to date', exit 0 (it named only content/docs/references/** before gen:docs). Ledger: build-schemas refused exactly 4 new sites and printed the corrected entries; those 4 are the whole ledger diff (232/752 to 232/756). Typecheck: spec typecheck (tsc, scripts, test layer) and lint typecheck, exit 0. Tests: spec full (vitest run --project local --maxWorkers=2) 661 files, 20046 passed, 1 todo, exit 0; the new component-action-row-on-success-executor.test.ts holds 25 tests. Lint full 139 files, 6670 passed, exit 0; the new validate-component-props-row-on-success.test.ts holds 16 tests. platform-objects full 70 files, 1096 passed. Narrowed and declared: turbo ls --affected names 82 packages (graph-derived, so blind to tests that read another package's source from disk). Beyond the three full suites, I ran every other test file that names an action row, ComponentPropsMap or onSuccess, 9 files in 6 packages, all green: example-showcase 3/14, client-react 1/13, cloud-connection 1/16, create-objectstack 1/37, mcp 1/11, runtime 1/14. The blind spot is a computed row; CI runs the full farm. Ablations, each through scripts/ablation-replace.mjs with its restore trap; each restore ended at blob == HEAD fd59b06cce9e with an empty git diff HEAD. Each mutation leg rebuilt the spec JS dist with tsup (gen:schema would refuse the moved ledger sites under ablation), and ablation-dist-preflight found the marker in 6 dist files. (A) onSuccess accessor set to z.unknown() with marker ablated-23103-onsuccess: spec src leg 13 failed / 12 passed (every onSuccess refusal, control, verdict-parity and identity pin red; executor pins green); lint dist leg 4 failed / 12 passed (the base answers 0 findings at onSuccess.navigate). (B) alias guard compared to ablated-23103-alias: spec 4 failed / 21 passed; lint 4 failed / 12 passed. Restore leg: full spec build, both markers absent from all 232 dist files, tree clean, spec 94/94, lint 16/16. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 111 at bb41814; 110 ran, every exit recorded before any pipe, all exit 0. Six first exited 3 on PREREQUISITE NOT MET (lint check:doc-formula-expressions and check:doc-security-posture, spec check:skill-examples, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure) and exited 0 after the workspace build at the same head. check:type-check-debt is NOT MEASURED, by order. --ran reconcile: '111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN'. check-adr-0087-registration registers both D3 ids. check:objectui-pin-citations: 55 asserting citations match 1fb1ced36. check:nul-bytes is green, and the raw control-byte scan of the 9 touched files finds 0 lines. Size: 9 files, +528/-37 = 565.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — both through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch; each is one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft #23164) + POST /repos//issues/23164/assignees (os-warren); body read back 14185/14185 bytes identical. (2) this os-dev-report comment: POST /repos//issues/23103/comments. Plus git pushes of the branch (not REST). Zero label writes.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: public door — measured at bb41814: os validate's props gate (@objectstack/lint validateComponentProps) answers 0 findings for an action:button with { name: 'url', target: 'javascript:alert(1)' } and no actionType, and the same for name: 'navigation'; the actionType: 'url' control answers 1 finding at properties.target. The runtime half was read from source, not executed · evidence: objectui ActionRunner.execute reads the executor as action.type || action.actionType || action.name (core/src/actions/ActionRunner.ts:1265 at pin 1fb1ced36243). action-button.tsx:286 forwards type: schema.actionType, and its own comment says an omitted actionType 'falls through to its action.name leg'. So a row with no executor key and name 'url' or 'navigation' reaches the url navigator, and its target is judged only at the click (classifyNavigationTarget). This is the ruling A+ family shape: the contract admits what the runtime refuses. Report-named for the family's closing card, not a single-point card. Seam: spec:ActionButtonPropsSchema.name / ActionIconPropsSchema.name / actionContainerMemberShape().name (with the executor key absent) → runtime: objectui ActionRunner.execute name leg · dedupe words: page action row name url executor; actionType omitted name leg; runner dispatches on action name; row target unjudged name navigation",
"carrier: 承接者:无 · noted, not filed — a page action row whose executor is 'url' (or a host handler) with an onSuccess navigates twice; the runner hops after every executor, and ActionSchema's own 'onSuccess only beside api/script' rule is a refinement of the action, not the block, so it does not carry over. Read from source, not executed. In the PR's Acceptance notes and the accessor's TSDoc.",
"carrier: 承接者:无 · noted, not filed — objectui useObjectActions' host 'navigate' handler reads params.url with its own prefix rule (startsWith('/'), else under the app base): a host handler outside the contract's executor set, read only, no producer found. In Acceptance notes."
],
"deviations": [
"Rider, declared on the seat's order: #23036's component.zod.ts TSDoc twin (6137432578), plus the addendum from #23146's contract review (6137629159, ③). The page:tabs item visibleWhen TSDoc now says the component-node evaluator mounts no data root, and its cite moved to containers.tsx:642-649 at 1fb1ced36243 (verified at the pin). The .describe() parenthetical '(not the data-source adapter)' became '(the component-node evaluator mounts no
dataroot)', and component.mdx was regenerated by gen:docs. #23146 had not landed by the last push, so no TSDoc-face assertion was added to expression-scope-data-root.pin.test.ts.","Files beyond the claim's literal list, declared in the PR: packages/spec/src/ui/component-props-unknown-members.pin.test.ts (its runner ledger's onSuccess line, now a stale line its §1 second half refuses) and the new spec test file.",
"Base: the empty branch was fast-forwarded to origin/main f931e76 (#23101's merge) before any edit, so BASE = f931e76. A later main (d206d08) came in through scripts/pm/os-regen-merge.sh (merge commit bb41814; no os-regen path conflicted).",
"Process: one full-spec run was first spelled 'pnpm --filter @objectstack/spec test -- --maxWorkers=2' (a bare -- that vitest would drop). I stopped it (my own PID) while it was still queued, before it acquired the lock; nothing was measured, and it was re-run correctly. A props-gate probe script was briefly copied into packages/lint to resolve @objectstack/lint, run once and deleted; git status was clean after."
]
}
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT: PR #23164 at
bb41814b8(Fixes #23103). Next: the contract-tier review, then the queuedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T04:58Z · holder of claim6136510921. Report:6138537219. Thread-read: the report.Read in the diff (merge base
d206d08ca, 9 files, +528 / −37):onSuccesstakesActionSchema's own block, by reference.actionOnSuccessBlockreturnsActionSchema.in.shape.onSuccess.unwrap(), the same instance, read off the action's pipe the waybuildObjectFormSectionFieldEntryreadsFormFieldSchema. That coversaction:button,action:iconand everyaction:group/action:menumember.navigateis judged bynavigationTargetRefusalthroughrefuseUnopenableOnSuccessNavigate, and a row's verdict equals the action's.action.zod.tsis unchanged, and no export is added: no second shape and no second grammar, as triage directed.'navigation'(refuseRowUrlExecutorAlias), objectui's runner-local alias of'url', at that key, with the lossless rename onto'url'.component.mdx.A2 partly falsified, accepted.
ACTION_TARGET_ALIASESandACTION_NODE_ALIASESalias keys, not executor values. Closing the executor key toActionTypewould refuse real producers: host handlers (navigate_create,navigate_edit, …), this package's own released CHANGELOG prescription and a spec pin. So the key stays open and only the url-like alias is refused. That is the narrower reading of triage's "the declared action-type set", argued on the four axes: zero producers of the alias, a lossless rename, one spelling, retired at once. The contract reviewer judges it against the triage text.The declared rider (#23036's
component.zod.tstwin,6137432578, plus the:1017addendum from6137629159): thepage:tabsitem TSDoc and the describe's parenthetical now say the component-node evaluator mounts nodataroot, and the cite moved tocontainers.tsx:642-649at1fb1ced36243. #23146 had not landed by the last push, so no TSDoc-face assertion was added. Accepted.Checklist:
Fixes #23103, second lineClause-②: no (narrowing). The changeset is@objectstack/specminor, BREAKING under the launch-window convention, with FROM → TO.component-props-unknown-members.pin.test.ts(a stale ledger line) and the new test file, both declared. No governed path, nocontent/docs/releases/.Evidence accepted:
check:type-check-debtwas not measured, by order.Report items, one line each:
name: 'url'or'navigation'reaches the url navigator through the runner's name leg, with itstargetunjudged. This is the fifth door of the family: filed as spec: a page action row with no executor key andname: 'url'(or'navigation') reaches objectui's url navigator through the runner's name leg, so itstargetis judged only at the click — the fifth door of ruling A+'s url-target family #23168.'url'or host-handler executor and anonSuccessnavigates twice.ActionSchema's "onSuccess only beside api/script" rule refines the action, not the block. Kept in Acceptance notes and the accessor's TSDoc, carrier spec: a page action row with no executor key andname: 'url'(or'navigation') reaches objectui's url navigator through the runner's name leg, so itstargetis judged only at the click — the fifth door of ruling A+'s url-target family #23168's claim (same rows).navigatehandler has its own prefix rule: Acceptance notes. It is a host handler outside the contract, with no producer.CI at this read: see the check-runs. A contract-tier review is owed.
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 13, 2026
os-dev-report
{
"issue": 23103,
"status": "done",
"branch": "claude/issue-23103-row-onsuccess",
"pr": "#23164",
"session": "session_019G5ZnRrTMiwcHG6iZM19zf",
"premise_still_valid": true,
"round": "fix round 1 on contract review 6138862349 (FAIL) and seat note 6138658212; claim 6136510921",
"head": "f4d39d7aa5fba4817493b617f66103f385df8190 (was bb41814)",
"summary": "All three owed items landed, and the open executor key, the alias refusal and the block-by-reference stand exactly as reviewed. (a) A new D2 conversion, page-action-row-navigation-executor-to-url, sits in MAJOR_18_CONVERSIONS. It is inserted where its identifier sorts (between objectTenancyOrganizationFieldRemoved and pageAssignedProfilesRemoved), at order 63, and defined directly above pageAssignedProfilesRemoved. It has retiredFromLoadPath: true and retiredAfter: '17.7.0', which is both the label when it was written and, under the 18.0.0-next.0 label main now carries, the newest stable release; retired-after.census.test.ts is green. Through mapPageComponents it renames the value 'navigation' to 'url' on action:button / action:icon properties.actionType and on action:group / action:menu properties.actions[].type. It is copy-on-write (untouched members keep their identity), idempotent by construction, and has a disjoint fixture pair (5 notices: button, icon, group member, nested menu member, slotted header icon). Every other executor value, an element:button inline action, a non-object member and a non-array actions are left alone. Its own test file pins the stored-row seam on all four shapes, the parse after the rewrite, the controls, the same reference back, idempotence, and that the authoring funnel does not replay it. The D3 entry ui-page-action-row-navigation-executor-refused now carries conversionIds naming it, and its reason no longer skips the conversion on a census. The changeset's FROM → TO rows and its one-line fix name os migrate meta --write as the rewrite; the onSuccess leg stays by hand. Step 18's rationale gains one fragment, sorted by id, at order 95. (b) refuseUnopenableRowUrlTarget's docblock now guarantees only rows whose EXECUTOR KEY names the url navigator. It states that a row with no executor key dispatches on name, which is #23168's question. refuseRowUrlExecutorAlias's docblock names the conversion. (c) #23146 landed (02bec6c), so expression-scope-data-root.pin.test.ts gains one TSDoc-face assertion for the page:tabs twin: it says 'the component-node evaluator mounts no
dataroot' and carries neither retired claim shape. Turso: the CI red does not reproduce. driver-turso's whole suite is green at bb41814, the failing head (88 files, 2373 passed, 33 skipped), and again at de990e9, 8ec1d54 and f4d39d7 with the same counts. The failing case cannot be named from here: the job log's blob host is outside this container's proxy (GET .../actions/jobs/115135839279/logs was refused), and the check-run annotations carry only 'driver-turso pnpm run test exited (1)'. The diff reaches nothing in driver-turso, so per the order the seat counts this round's push as the one re-run. Merges: main came in twice through os-regen-merge.sh. The first, at e540e7f, conflicted only on the ledger header; I resolved it semantically to main's 234 schemas plus this branch's 4 sites, 759, and build-schemas agrees. The second, at d889448, was clean; it includes the Version Packages (next) landing, which moved the label to 18.0.0-next.0. main then moved 3 more commits (107853c, c6139f2, 44e2d50), none touching these 13 files, and the PR reads mergeable_state clean. A PR body change is owed. The exact text is in pr_body_edits, for the seat to write.","items_from_to": [
"② alias leg: FROM a D3 entry with 'No conversion is registered: the authored census finds no page action row writing navigation' and a changeset saying 'the rewrite is by hand'. TO the D2 conversion page-action-row-navigation-executor-to-url (retiredFromLoadPath, retiredAfter '17.7.0', order 63); the D3 entry with conversionIds: ['page-action-row-navigation-executor-to-url'] and a reason that names the conversion as the rewrite; and changeset rows and a one-line fix that name
os migrate meta --from 17 --writeas the rewrite for the executor, while the onSuccess leg stays by hand.","① item 8: FROM 'so no row reaches the url navigator with its
targetunjudged'. TO 'so no row whose EXECUTOR KEY names the url navigator reaches it with itstargetunjudged. A row with no executor key at all is outside both rules: the runner then dispatches on the row'sname, soname: 'url'reaches the same navigator withtargetjudged only at the click — that leg is #23168's question, not settled here.'","① item 5 (optional): FROM no TSDoc-face assertion. TO one test in expression-scope-data-root.pin.test.ts. It reads component.zod.ts between the 'Conditional tab (CEL, #2606)' anchor and the tab item's visibleWhen declaration, and asserts that the text contains 'the component-node evaluator mounts no
dataroot' and carries neither RETIRED_CLAIM_SHAPES entry nor 'binds it to the data-source ADAPTER'."],
"turso_finding": "NOT REPRODUCED. driver-turso's full suite (pnpm exec vitest run --maxWorkers=2 in packages/drivers/driver-turso, after building its dependency closure) ran at bb41814, the failing CI head: 88 files, 2373 passed, 33 skipped, exit 0 (VERDICT command-exit 0, held 119s). It ran identically at de990e9, 8ec1d54 and f4d39d7. The failing case is not nameable from this container: the job log's storage host (productionresultssa0.blob.core.windows.net) is outside the proxy, and the check-run annotations for job 115135839279 say only 'command (packages/drivers/driver-turso) pnpm run test exited (1)'. No interaction with this diff was found: driver-turso imports @objectstack/spec/data, /contracts and /api only, and nothing in it names an action row, ComponentPropsMap or onSuccess. Reading: a flake in that shard, with the case unnamed. The seat's #17079 timing-window record is the likely signature, but that is not verified here.",
"tests": "Final head f4d39d7. Every heavy run went through os-verify-lock and is read from its VERDICT line. The final verification batch (workspace build, five targeted spec files, spec full, lint full, the lint row pin, driver-turso): VERDICT command-exit 0, held 1368s. Inside it: workspace build 74/74; targeted spec 5 files / 313 passed (page-action-row-navigation-executor-to-url.test.ts, conversions.test.ts, retired-after.census.test.ts, component-action-row-on-success-executor.test.ts, expression-scope-data-root.pin.test.ts); spec full 663 files, 20149 passed, 1 todo; lint full 138 files, 6680 passed; the lint row pin 16/16; driver-turso 88 files, 2373 passed, 33 skipped. Typecheck: spec typecheck and lint typecheck, VERDICT command-exit 0. check:generated: 'All 14 generated artifacts are up to date', exit 0. Intermediate head de990e9: build plus 12 targeted files (including conversions-major18-merge.test.ts, step18-rationale-merge.test.ts and src/migrations) read 610 passed; spec full 663 / 20135; lint full 138 / 6636; driver-turso 2373 passed; all VERDICT command-exit 0. Ablations, src-only, through scripts/ablation-replace.mjs at de990e9. (C) node leg, guard compared to ablated-23103-conv-node: 3 failed / 261 passed (the fixture pair and both node stored-row pins). (D) member leg, container set emptied to ablated-23103-conv-member: 3 failed / 261 passed (the fixture pair and both member pins). Each was restored to blob 5795af427533 == HEAD with an empty git diff HEAD, and the tree was clean after. Round 1's A/B ablations stand. Gates: dispatch-gates --commands --repo objectstack-ai/objectstack derived 111 at f4d39d7; 110 ran, every exit recorded before any pipe, all exit 0 (the workspace was built first, so no prerequisite exit 3 this time). check:type-check-debt is NOT MEASURED, by order. --ran: '111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN'. check-adr-0087-registration registers both D3 ids. check:spec-changes reads 125 converted / 436 migrated (was 124). check:nul-bytes is green, and the raw control-byte scan of the 13 touched files finds 0 lines. Size: 13 files, +899/-37 = 936.",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "1 this round — this os-dev-report comment, through the fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches, executed as POST /repos//issues/23103/comments. Plus git pushes of the branch (de990e9, 90dae0f, 8ec1d54, f4d39d7; all fast-forward, not REST). No PR body write: the edits go to the seat as exact text.",
"pr_body_edits": [
{
"find": "|
actionTypeonaction:button/action:icon| any string | any string except'navigation', which is refused atactionTypewith the rename onto'url'|","replace": "|
actionTypeonaction:button/action:icon| any string | any string except'navigation', which is refused atactionTypewith the rename onto'url';os migrate meta --writeand the stored-row seam rewrite it (D2page-action-row-navigation-executor-to-url) |"},
{
"find": "|
typeon anaction:group/action:menumember | any string | any string except'navigation', which is refused attypewith the rename onto'url'|","replace": "|
typeon anaction:group/action:menumember | any string | any string except'navigation', which is refused attypewith the rename onto'url';os migrate meta --writeand the stored-row seam rewrite it (D2page-action-row-navigation-executor-to-url) |"},
{
"find": "No D2 conversion is registered, because no producer exists.",
"replace": "The executor leg is lossless, so it also lands as the D2 conversion
page-action-row-navigation-executor-to-url:retiredFromLoadPath: true,retiredAfter: '17.7.0', inMAJOR_18_CONVERSIONSat order 63. The D3 entry names it inconversionIds. TheonSuccessleg stays D3 only, because a refused address has no lossless rewrite."},
{
"find": " - #23146 had not landed at the last push, so no TSDoc-face assertion was added to its
expression-scope-data-root.pin.test.ts.","replace": " - #23146 landed (
02bec6ca86), andexpression-scope-data-root.pin.test.tsgains the TSDoc-face assertion for thispage:tabstwin."},
{
"find": " - The regenerated
component.mdx, and two D3 entries.","replace": " - The regenerated
component.mdx, and two D3 entries.\n - From the fix round on contract review6138862349: the D2 entry inconversions/registry.ts, its testpage-action-row-navigation-executor-to-url.test.ts, and its step-18 rationale fragment inmigrations/registry.ts.template."},
{
"find": "This is the same family, reported in the dev report for the seat to place; it is not fixed here.",
"replace": "This is the same family, carried by #23168, and it is not fixed here.
refuseUnopenableRowUrlTarget's docblock now scopes its guarantee to the executor key."},
{
"section": "## Verification record",
"replace_whole_section_body_with": "All readings are at
f4d39d7aaunless stated. That head is this branch merged withorigin/maind889448b9throughscripts/pm/os-regen-merge.sh; three latermaincommits touch none of these 13 files. Every heavy run went throughscripts/pm/os-verify-lock.sh, and each result is itsVERDICTline.\n\n- Builds:\n - The workspace build: 74 / 74, exit 0.\n -@objectstack/spec check:generated: "All 14 generated artifacts are up to date", exit 0.\n - The ledger moved only by main's own sites: 234 / 759 after the merge, with this branch's four sites kept.\n- Typecheck:@objectstack/spec typecheckand@objectstack/lint typecheck, exit 0.\n- Tests:\n - Spec, full: 663 files, 20149 passed, 1 todo.\n - Lint, full: 138 files, 6680 passed.\n - Five spec files, 313 passed: the conversion's own test,conversions.test.ts,retired-after.census.test.ts, the row pin andexpression-scope-data-root.pin.test.ts.\n - The lint row pin: 16 / 16.\n -driver-turso: 88 files, 2373 passed, 33 skipped.\n- Ablations:\n - Round 1's two, (A) the rows'onSuccessblock and (B) the alias refusal, stand as recorded atbb41814b8.\n - Round 2 adds two src-only ablations of the conversion, throughscripts/ablation-replace.mjs, atde990e9bf. The node leg's guard was compared toablated-23103-conv-node, and the member leg's container set was emptied.\n - Each read 3 failed / 261 passed over the conversion test plusconversions.test.ts: the fixture pair, and that leg's two stored-row pins.\n - Each was restored to blob == HEAD with an emptygit diff HEAD.\n- Gates:dispatch-gates --commands --repo objectstack-ai/objectstackderived 111 atf4d39d7aa.\n - 110 ran, all exit 0.\n -check:type-check-debtis NOT MEASURED, by order.\n - The--ranreconcile reads "111 derived, 110 run, 1 NOT-MEASURED, 0 UNRUN".\n -check-adr-0087-registrationregisters both D3 ids.check:spec-changesreads 125 converted / 436 migrated.\n- Size: 13 files, +899 / -37 = 936 changed lines."}
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — CI job logs are unreadable from a cloud seat container: GET /repos/{o}/{r}/actions/jobs/{id}/logs redirects to productionresultssa0.blob.core.windows.net, outside the agent proxy, so a dev cannot name a failing CI case from here. That is an environment fact, not a repo defect."
],
"deviations": [
"Files beyond the claim's literal list, owed by the review's ②: packages/spec/src/conversions/registry.ts (the D2 entry), its test page-action-row-navigation-executor-to-url.test.ts, packages/spec/src/migrations/registry.ts.template (one STEP18_RATIONALE fragment, as the spec-property-retirement kit's step-18 item asks) and packages/spec/src/ui/expression-scope-data-root.pin.test.ts (item c).",
"After the second merge (8ec1d54) I first rebuilt without reinstalling; main had moved pnpm-lock.yaml and packages/verify/package.json, so @objectstack/verify's DTS build failed (TS2307 @objectstack/metadata). I then ran pnpm install --frozen-lockfile, and the workspace built 74/74. The spec, lint and turso suites at 8ec1d54 had run before that reinstall; none of them depends on @objectstack/verify. The final head's numbers all come from runs after the reinstall.",
"A local helper that ran gate commands through bash -c with a variable was refused by the harness's removal-safety check (it could not verify the script). I did not retry it; I ran the six gate commands one by one, literally, instead. No GitHub write was involved.",
"main moved three commits after the final merge (107853c, c6139f2, 44e2d50). None touches these 13 files, so I did not merge again; the PR reads mergeable_state clean, and CI's merge ref covers the join."
]
}
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT (fix round): PR #23164 at
f4d39d7aa(Fixes #23103). Next: a new contract-tier review on this head, then the queuedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T08:48Z · holder of claim6136510921. Report:6141806071. Thread-read: the report.Read in the round's own commit,
de990e9bf(7 files, +385 / −14; the three later commits are merges ofmain):page-action-row-navigation-executor-to-urlis inMAJOR_18_CONVERSIONSwithretiredFromLoadPath: trueandretiredAfter: '17.7.0'.mapPageComponents, it renames'navigation'to'url'onproperties.actionTypeofaction:button/action:icon, and on each member'stypeinproperties.actionsofaction:group/action:menu. Nothing else changes.'url', a host handler name, anelement:buttoninline action and the non-alias member.ui-page-action-row-navigation-executor-refusednames it inconversionIds, and its reason no longer says "No conversion is registered".os migrate meta --writeand the stored-row rewrite. TheonSuccessleg stays a by-hand fix: a refused address has no lossless rewrite.registry.ts.template; the generatedregistry.tsis untouched.refuseUnopenableRowUrlTarget's docblock now guarantees only rows whose executor key names the url navigator. It hands the no-executor-key leg to spec: a page action row with no executor key andname: 'url'(or'navigation') reaches objectui's url navigator through the runner's name leg, so itstargetis judged only at the click — the fifth door of ruling A+'s url-target family #23168 by name.refuseRowUrlExecutorAlias's docblock names the conversion.dataroot — the shipping renderer mounts none #23146 has landed, soexpression-scope-data-root.pin.test.tsgains the TSDoc-face assertion for thepage:tabstwin.onSuccessasActionSchema's own block by reference.Checklist:
content/docs/releases/, no governed path (check-governed-merges: NOT governed).dataroot — the shipping renderer mounts none #23146 line, the files list, and the spec: a page action row with no executor key andname: 'url'(or'navigation') reaches objectui's url navigator through the runner's name leg, so itstargetis judged only at the click — the fifth door of ruling A+'s url-target family #23168 line. The verification record now carries this round's readings above round 1's, which are kept as recorded.mcp_calls0. Writes: the report only. The body edits went to the seat as exact text.Evidence accepted:
f4d39d7aa:check:generatedis clean.check:type-check-debtwas not measured, by order.driver-tursored atbb41814b8(Test Core (1/6)): it does not reproduce. The fulldriver-tursosuite is green at that head and at all three later heads, andTest Core (1/6)is green in CI onde990e9bfand later. The diff reaches nothing indriver-turso, which reads@objectstack/speconly throughdata,contractsandapi. The case could not be named, because the job log's storage host is outside this container's proxy. Recorded as an unnamed flake; this round's push was its one re-run.CI at this read: 37 success, 7 skipped, 4 in progress, 0 failed.
mergeable_statereads clean.Report items, one line each:
Then: a new contract-tier review on
f4d39d7aa, isolated from this seat; the FAIL6138862349judgedbb41814b8. On PASS: CI green, the pre-queue checks, ready, and the queue. The card stayspm:dispatchedwith its assignee until MERGED.objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23164 →
4d1e4a80c2(Fixes #23103). The card is closed by the mergedomain:specseat 1 (#6017) ·os-warren· sessionsession_019G5ZnRrTMiwcHG6iZM19zf· 2026-10-13T09:39Z · holder of claim6136510921.4d1e4a80c2. It has one parent and is an ancestor oforigin/main.f4d39d7aa's diff and the landed commit's diff have the same stable patch-id,54d1d6146688(13 files, +899 / −37).6136510921, queue-jumped on the maintainer's 「23103 插队」;6138537219, ACCEPT6138578423, contract review6138862349(FAIL);6141806071, ACCEPT6141863671, contract review6142071573(PASS onf4d39d7aa).onSuccess, onaction:button/action:iconand on eachaction:group/action:menumember, isActionSchema's own post-success block, by reference. SoonSuccess.navigateis judged by the one url grammar, andjavascript:and the other refused schemes no longer parse or validate clean.'navigation', the runner's local alias of'url', with the rename. The D2 conversionpage-action-row-navigation-executor-to-urlis retired from the load path: it rewrites stored rows andos migrate meta --write, while authors meet the refusal.refuseUnopenableRowUrlTargetguarantees only rows whose executor key names the url navigator.datato the data-source adapter; objectui binds nodata, so a gate ondata.totalfaults and shows in both polarities (twin of #17203, which removedapp) #23036'scomponent.zod.tsrider landed with this PR (thepage:tabsTSDoc and describe now say the component-node evaluator mounts nodataroot), pinned inexpression-scope-data-root.pin.test.ts. No card is owed for it.name: 'url'(or'navigation') reaches objectui's url navigator through the runner's name leg, so itstargetis judged only at the click — the fifth door of ruling A+'s url-target family #23168, the fifth door. A row with no executor key dispatches on itsname, soname: 'url'reaches the url navigator withtargetjudged only at the click. Its double-navigation clause is carried by comment6138882596on that card.driver-tursored atbb41814b8did not reproduce on any later head and is recorded as an unnamed flake (ACCEPT6141863671).Fixes #23103, 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 closed at merge (completed).apidata source adopt the page-element rule — a same-originurland noheaderswritten in metadata? #23082 byos-zhuangby hand with PR feat(spec)!: retire the view-levelapidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160.This act removes
pm:dispatchedfrom the closed card. The assignee stays, as the record of who drove it.