Skip to content

feat(spec): enforce the documented newTabUrl / opensInNewTab co-constraint on ActionSchema - #12108

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-11842-newtaburl-refine
Aug 25, 2026
Merged

os-litant merged 2 commits into
mainfrom
claude/issue-11842-newtaburl-refine

Conversation

@os-litant

Copy link
Copy Markdown
Contributor

Fixes #11842

What

ActionSchema.newTabUrl's doc has always said "Only valid together with opensInNewTab", but nothing on the refine chain enforced it: an action declaring newTabUrl without opensInNewTab: true parsed clean while no renderer path ever read the key — the ADR-0078 declared-but-unenforced shape, arriving through a documented co-constraint rather than a missing key.

A new .refine on ActionSchema (after the #11519 doubled-channel rule, before the lowerRequiresFeature transform) now rejects newTabUrl whenever opensInNewTab !== true, with a message located on path: ['newTabUrl'] naming the pre-opened-tab contract and both remedies (add opensInNewTab: true if a pre-opened tab is intended; otherwise drop the inert key — behavior unchanged, the lone key was never read).

Deliberate scope points, pinned in the tests:

Corpus sweep (re-run at claim, per triage)

Zero lone-newTabUrl producers found anywhere reachable:

  • this repo (*.ts/tsx/json/md/mdx, examples/, platform metadata, fixtures): the only metadata-shaped occurrence is the legal pair in action-doubled-redirect.test.ts:101-107; the rest is docs prose and the generated reference — all already stating the pairing.
  • objectui (local sibling checkout): consumer-only; the single read point (app-shell/src/utils/consoleServerAction.ts:188) reads action.opensInNewTab && newTabUrl, confirming the lone key is inert at the renderer; every test fixture declares the pair.
  • cloud: not present in this container; the issue's measurement of record stands (the cloud SSO producers declare the pair correctly).

skills/objectstack-ui/SKILL.md and content/docs/protocol/objectui/actions.mdx already document the constraint ("It is valid only alongside opensInNewTab: true") — description consistency verified, no doc edits needed, no skills/** diff.

Verification (all at final head a9e2a0f unless noted)

  • @objectstack/spec full suite: 426 files / 11329 tests passed (run at pre-merge tree 1b3c10d; the merge of origin/main brought zero packages/spec paths, so the tree the suite judged is content-identical for spec) · targeted re-run at a9e2a0f: action-newtaburl-pair + action-doubled-redirect + action.test — 3 files / 164 tests passed.
  • pnpm --filter @objectstack/spec typecheck (tsc + check:scripts-typecheck + check:test-typecheck): green — the new test file compiles under tsconfig.test.json, debt ledger unchanged (55 files / 263 errors).
  • check:generated: all 14 artifacts up to date. check:api-surface initially flagged stale purely via its mtime freshness predicate (the reverse-verification churned action.zod.ts timestamps); after rebuild, gen:api-surface reproduced the snapshot byte-identically (17 entries, 5165 exports; empty git diff) — the refine changes no exported surface.
  • Reverse verification (fix committed first; trap-guarded script): reverted action.zod.ts to base f7b25c5 — mutation proven on disk by marker count (grep -c '11842': 2 → 0); the 5 refusal tests turned red, the 3 legal-pair pins stayed green (5 failed | 3 passed); restored from HEAD (marker 2 again, clean porcelain) — 8/8 green. No dist leg applies: the suite imports ./action.zod source directly (in-package relative import, no exports-resolved dist path).
  • Gate families derived from the actual diff (scripts/pm/dispatch-gates.mjs, derivation stamped at a9e2a0f), all green by their own printed verdicts at the final head: changeset-gate-self-tests · objectui-changeset · changeset-no-major · empty-changeset · release-rehearsal-clone --self-test · merge-driver · published-files · slot-lookup · spec-parsed-alias · test-source-alias · type-source-resolution · query-options-erasure · engine-double-contract · where-matcher · cross-package-test-inputs · ci-filter-parity · plugin-teardown-shape · nul-bytes · doc-formula-expressions · spec empty-state / liveness / strictness-ledger / variant-docs · check-adr-0087-registration · type-check-coverage · docs-audit (affected-docs + drift-comment).
  • Not measured locally, declared: check:dev-prereqs and check:type-check-debt --re-measure both refuse on this worktree's unbuilt sibling packages (66/67 dists absent — a workspace-state precondition, not a finding; CI builds fresh). Compensating evidence for the ratchet: the only test-layer change compiles clean under spec's test program (check:test-typecheck green above), so no upward drift direction exists.

Changeset / ADR-0087

@objectstack/spec: minor, declared BREAKING accept-set narrowing (launch-window convention), disposition not-required (no-migration-prescription) — validated green by check-adr-0087-registration. Mirrors the adjacent #11519 narrowing on the same schema: no key removed/renamed/re-shaped, the parse-site refusal is the channel that reaches an affected author, and the measured population of affected sources is zero in every corpus. Consequence: no migrations registry.ts edit, so this PR has no shared-file overlap with batch siblings.

Note: #11519 is referenced above as precedent only and remains closed on its own PR; no other card is addressed here.


Generated by Claude Code

claude added 2 commits August 25, 2026 08:14
…raint on ActionSchema

A refine now rejects an action declaring newTabUrl whose opensInNewTab is
not true, with guidance naming the pre-opened-tab contract and both
remedies. Legal pairings are pinned byte-identically; corpus measured at
zero lone producers (re-measured at claim).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/deployment/troubleshooting.mdx (via ActionSchema (symbol))
  • content/docs/protocol/objectui/actions.mdx (via ActionSchema (symbol), newTabUrl (literal))

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

  • content/docs/releases/v17.mdx (via ActionSchema (symbol))

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
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: 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.

Coarse fallback — 126 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 a933ed720ec240e154fdfe433a7aced48b557700 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 750f64bd2145bc703245625d7d4f94fc34bc956c — the merge of head a9e2a0ff406101f74352203423c3303c9e581025 into base a933ed720ec240e154fdfe433a7aced48b557700, 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 750f64bd2145bc703245625d7d4f94fc34bc956c && git checkout 750f64bd2145bc703245625d7d4f94fc34bc956c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a933ed720ec240e154fdfe433a7aced48b557700 a9e2a0ff406101f74352203423c3303c9e581025 && git checkout -B drift-repro a933ed720ec240e154fdfe433a7aced48b557700 && git merge --no-ff a9e2a0ff406101f74352203423c3303c9e581025

node scripts/docs-audit/affected-docs.mjs --json a933ed720ec240e154fdfe433a7aced48b557700

⚠️ 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 a933ed720ec240e154fdfe433a7aced48b557700 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:ui tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Aug 25, 2026
@os-litant os-litant added the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Aug 25, 2026 — with Claude
@os-litant os-litant removed the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Aug 25, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review August 25, 2026 09:28
@os-litant
os-litant added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit 2d4fa75 Aug 25, 2026
39 checks passed
@os-litant
os-litant deleted the claude/issue-11842-newtaburl-refine branch August 25, 2026 09:58
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 Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ActionSchema documents "newTabUrl is only valid together with opensInNewTab" but no refine enforces the pair (observation)

2 participants