Skip to content

fix(spec): register the newTabUrl path refusal as an ADR-0087 D3 entry; the form label says path - #27282

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-ev6wfx
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-ev6wfx

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread
Refs #26924

Clause-②: no
Metadata-protocol: none
Skills-docs: none — no skills/** file changes; the one sentence that teaches newTabUrl (skills/objectstack-ui/rules/actions.md) already teaches a backend path since #27252. Not Tier H.

This PR carries the contract-review FAIL items of record 6470108120 on #27252, found after the maintainer had queued #27252 (queued 13:21Z, record 13:21Z, merged 13:42Z as c13ef04c3).

维护者速读

What changes

  • ADR-0087 registration. New D3 semantic entry packages/spec/src/migrations/entries/semantic/18.ui-action-new-tab-url-path-refused.ts (protocol 18), following the in-tree 18.*-refused.ts shape (18.ui-action-undoable-unfulfillable-refused.ts):
    • surface: an action document (standalone, an object's actions[], or type-level) whose newTabUrl does not start with /.
    • replacement: the backend path (newTabUrl: '/sso-open/{recordId}'), or for an external page drop opensInNewTab / newTabUrl and declare a type: 'url' action (target: 'https://example.com/export/${ctx.recordId}', openIn: 'new-tab'). Stated as not mechanically convertible, a structured TODO: an external URL names no path on this backend, and the url-action rewrite changes type, drops the action's own target / body handler and replaces the auth-enforcing endpoint with a page the browser opens directly, so which remedy the author meant is not recorded anywhere.
    • reason and acceptanceCriteria: the console's origin-prefix behaviour, why the set is closed rather than widened, and the stored-row behaviour already pinned by fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252 (loads unchanged and registered, invalid with a [metadata_spec_invalid] boot line, _diagnostics at newTabUrl, next save 422 INVALID_METADATA).
    • ActionSchema.newTabUrl's JSDoc names the entry.
  • Changeset. The merged .changeset/26924-new-tab-url-backend-path.md is not edited (check-empty-changeset refuses edits to a changeset the merge base carries). A new .changeset/26924-new-tab-url-d3-entry.md (@objectstack/spec patch, @objectstack/platform-objects patch) carries adr-0087: registered ui-action-new-tab-url-path-refused and a "Supersedes, in the same release" paragraph that replaces the earlier not-required disposition. Both packages are unreleased since fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252, so the two notes ship together.
  • Docs. content/docs/protocol/objectui/actions.mdx called newTabUrl a "new-tab target template", which reads as a URL after fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252; it now says a path on this backend starting with /, and points an external page at a type: 'url' action with openIn: 'new-tab' (flagged by the docs-drift check on this PR).
  • Form label. action.form.ts labels newTabUrl "New-tab path"; en.metadata-forms.generated.ts is regenerated by node scripts/check-i18n-bundles.mjs --write. zh-CN 「新标签页路径」, ja-JP 「新しいタブのパス」, es-ES "Ruta de pestaña nueva": translated-locale values are hand-authored (AGENTS.md documentation guardrails: the generator rewrites only en and keeps every translated value), so they are edited in place and the generator re-run reports them in sync.

Tests

  • No new test: the entry is data the registry tests read. gen:migration-registry then src/migrations + src/conversions → see Verification.

Acceptance notes

  • No refused-shapes.ts detector is added for the entry; the review asked for the D3 entry. A detector would make os migrate meta --stored list each stored row by name; that can follow if the seat wants it.
  • The missing skills/objectstack-ui/evals case for newTabUrl stays with the lane seat, as noted at ACCEPT.

Verification

All readings on a shared container, base main 7858c1d16.

Head 78e8f9751: two commits on 7858c1d16, 9 paths. 0c704ce93 carries the entry, the changeset and the labels (8 paths, +61/-7); 78e8f9751 is the one-line docs edit.

  • Locked run (os-verify-lock, one script): install, gen:migration-registry, build, check:generated exit 0; @objectstack/spec vitest 748 files / 22349 tests passed; @objectstack/platform-objects 96 files / 1361 tests passed; CLI build, then check:i18n OK (en regenerated, translated locales in sync). VERDICT command-exit 0, 891 s.
  • src/migrations + src/conversions after gen:migration-registry: 28 files / 945 tests passed; the generated registry carries ui-action-new-tab-url-path-refused.
  • node scripts/check-adr-0087-registration.mjs --base origin/main exit 0, which does not judge this marker: the gate reads only changesets that declare a breaking change, and this one is a patch. The marker was checked by hand instead: the entry file name, its exported id and the marker all read ui-action-new-tab-url-path-refused, and the generated registry carries it. check-empty-changeset exit 0 (the merged changeset is untouched).
  • Derived gates: on 0c704ce93, 80 families were derived and all 80 ran serially, every one exit 0. With the docs commit the derivation is 101; the 21 new families plus every docs/link/affected-docs family (31 in all) were rerun on 78e8f9751, every one exit 0. dispatch-gates --ran on 78e8f9751: 101 derived, 101 run, 0 NOT-MEASURED, 0 UNRUN.
  • Not run here: the full dogfood suite (this diff moves no runtime path; it ran on fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252 at 348 passed / 3 skipped).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq

…; the form label says path

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:ui tooling labels Oct 28, 2026
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 5 documentable anchor(s).

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

  • content/docs/protocol/objectui/actions.mdx (via newTabUrl (literal, a string literal in sections))

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

  • content/docs/releases/v17/17-3.mdx (via newTabUrl (literal, a string literal in sections))

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
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 56 of 208 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 — 146 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 0d45c0e71f0a03bdcf2230901515c2a29716a92b → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 0d45c0e71f0a03bdcf2230901515c2a29716a92b

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

The actions page called newTabUrl a "new-tab target template", which
reads as a URL now that only a path starting with / is accepted. Say it
is a path on this backend and point an external page at a url action.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 78e8f97515f54992d16469102d21f461840d42a0
Local-runs: none
Scope: packages/spec/src/migrations/entries/semantic/18.ui-action-new-tab-url-path-refused.ts, packages/spec/src/ui/action.form.ts, packages/spec/src/ui/action.zod.ts + .changeset/26924-new-tab-url-d3-entry.md, content/docs/protocol/objectui/actions.mdx (the hand-written sentence the second commit corrects), and read in the cited code: packages/spec/src/migrations/types.ts (SemanticMigration), packages/spec/src/migrations/entries/semantic/18.ui-action-undoable-unfulfillable-refused.ts (precedent), packages/spec/scripts/build-migration-registry.ts (entry discovery), packages/spec/src/migrations/spec-changes.ts and migrations.test.ts (every hop-18 semantic entry is emitted as a TODO), scripts/check-empty-changeset.mjs (the foreign-changeset rule), scripts/check-adr-0087-registration.mjs (registered resolution, G1), docs/adr/0087-metadata-protocol-upgrade-contract.md, packages/spec/src/kernel/protocol-version.ts, ui/packages/app-shell/src/utils/consoleServerAction.ts, the .changeset/26924-*.md files on origin/main. Outside this scope — the four *.metadata-forms.generated.ts bundles — is the lane seat's ACCEPT at TIER_DEFAULT.

Round 2 of record 6470108120 on #27252 (merged 13:42Z as c13ef04c3, queued before that record posted). Inputs: card #26924 (every comment through the landing note 6471217433), PR #27282 body, file list (9 paths) and net diff against merge base 7858c1d16 (+62/-8), and the check-runs on this head. The head reviewed is the PR's current head: two commits on 7858c1d16 — 0c704ce93 (entry, changeset, labels, JSDoc; 8 paths) and 78e8f9751 (one sentence in content/docs/protocol/objectui/actions.mdx). The order named 0c704ce93; its runs were cancelled by the concurrency group when 78e8f9751 was pushed at 14:29Z (TypeScript Type Check closed as failure on cancelled lanes, Build Core and the UI contexts cancelled), so that head carries no gate verdicts and no record can be rendered on it; the queue guard reads the record for the current head. Gates on 78e8f9751, read to completion (54 check-runs, 0 pending at 14:53Z): every one of the ten required contexts is success — Lint & Repo Gates, TypeScript Type Check (check:docs, check:generated and the ADR-0087 gate ride in it), Test Core (8/8 shards), Build Core, Governed Surface Queue Guard, Build & E2E UI, Type Check UI, Test UI, Live E2E UI — and Lint UI is skipped by its paths filter (no ui/** on the file list). Check Changeset, Check Documentation Links, Flag docs affected by code changes and Spec property liveness are success; no check-run is failure or cancelled. Those conclusions are the gate verdicts here.

① Derived judgments

  • The FAIL item of 6470108120 is closed — right. 18.ui-action-new-tab-url-path-refused.ts exports a SemanticMigration with the four load-bearing fields (types.ts:99-112): surface names the action documents (standalone, actions[], type-level) whose newTabUrl does not start with /; replacement names both remedies — the backend path, or a type: 'url' action with target: 'https://…${ctx.recordId}' and openIn: 'new-tab' — and states "NOT mechanically convertible, so this entry is a structured TODO", with the reason a conversion is not written (changes type, drops the action's own handler, replaces an auth-enforcing endpoint); reason states the origin-prefix behaviour (consoleServerAction.ts:209, unchanged) and why the set was closed rather than widened; acceptanceCriteria restates the stored-row behaviour fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252 pinned. Same shape as the precedent entry, conversionIds rightly omitted (it judges no mechanical edit). File prefix 18. matches PROTOCOL_VERSION 18.0.0; build-migration-registry.ts discovers the directory, so the entry enters MIGRATIONS_BY_MAJOR[18].semantic and migrations.test.ts:705-706 holds that the chain emits every such id — the entry reaches os migrate meta and the upgrade guide with no companion detector owed (the precedent has none either).
  • The marker — right in substance, verified by hand. .changeset/26924-new-tab-url-d3-entry.md carries exactly one marker, adr-0087: registered ui-action-new-tab-url-path-refused, and the id resolves to the entry added in this diff. Note for the record: this changeset declares no breaking change, and check-adr-0087-registration.mjs judges only changesets that do (its G1 battery: "a non-breaking changeset is not this gate's business"), so the PR body's reading "exit 0 (reads the registered marker against the entry)" is a gate that did not look, not one that agreed. The id was checked here instead: entry file, exported id, file name and marker agree. Nothing to change — declaring the changeset BREAKING to make the gate look would be false.
  • Accept set, authorable surface, published schema — unchanged; right. action.zod.ts moves two JSDoc lines only (the stored-row paragraph now names the D3 entry); .describe() is byte-identical, so the JSON Schema, authorable-surface shards and generated reference pages do not move. action.form.ts changes the newTabUrl label only; help text and visibleWhen unchanged. No ui/** change; the renderer's behaviour fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252 documented still holds.
  • Form label — right, closes the should-fix. "New-tab path" (en, regenerated from source), 「新标签页路径」 (zh-CN), 「新しいタブのパス」 (ja-JP), "Ruta de pestaña nueva" (es-ES), keys unchanged, the translated values hand-edited as the Documentation Guardrails row prescribes. git grep on the head for "New-tab URL", 「新标签页 URL」, 「タブの URL」 and "URL de pestaña" across packages, ui, content and skills (CHANGELOGs excluded): 0 hits.
  • The docs sentence (78e8f9751) — right. content/docs/protocol/objectui/actions.mdx:110 now says newTabUrl "skips the round-trip with a path on this backend, starting with / ({recordId} placeholder); an absolute URL is refused, so an external page is a type: 'url' action with openIn: 'new-tab'" — the same meaning as the schema's .describe(), the form help and the skills sentence, and the page is in a hand-written tree (not references/, not releases/), so the edit is allowed there and belongs with the change that falsified the old wording.
  • Nothing fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252 shipped regresses — right. Nine paths: one new entry, one new changeset, one JSDoc edit, one label, one docs sentence, four translation bundles. The regex, prescription, describe, skills sentence, liveness rows, dashboard prose and citations are untouched on this head.

② Semver level

  • Clause-②: no and Metadata-protocol: none on the PR body and the changeset — right: no key, value or meaning an author can write moves. @objectstack/spec patch (a registry entry and a JSDoc line in a published package) and @objectstack/platform-objects patch (label strings in the shipped bundles) — right; no skip-changeset.
  • Superseding the merged disposition — right, and the only available form. .changeset/26924-new-tab-url-backend-path.md (the not-required note) and .changeset/26924-platform-objects-new-tab-url-help.md are both still present on origin/main, so neither has been consumed by a release: the two notes ship in the same changeset version. Editing the merged note here is refused by check-empty-changeset's foreign-changeset rule (finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712: a PR may not MODIFY or DELETE a .changeset/*.md that exists on the merge base and it did not add), and the ADR-0087 gate is forward-only (a row already breaking at the merge base is never re-judged), so a new changeset that states "Supersedes, in the same release, the ADR-0087 disposition of the newTabUrl narrowing note … now registered" is the one channel that reaches the CHANGELOG reader and the registry. Its text names the superseded note by its summary line, the entry id, both remedies, why neither is mechanical, and the unchanged stored-row behaviour; nothing in it contradicts the merged note's facts. Right.

③ Boundary flags

  • No refused-shapes.ts detector for the entry (dev acceptance note) — accepted: the chain emits the entry as a TODO on every hop-18 replay without one (spec-changes.ts:260), the precedent entry has none, and the record asked for the D3 entry. A --stored row-by-name listing is a lane follow-up, not owed here.
  • The merged not-required disposition stays in the merged note — accepted as above; the superseding paragraph is the correction the release carries.
  • PR body's "the gate reads the registered marker" — should-fix in prose only (the body is not a shipped artifact): the gate skipped a non-breaking changeset; the marker was verified here by hand. No code change.
  • Docs Drift Check (6471576660) names content/docs/protocol/objectui/actions.mdx — answered by 78e8f9751: the "new-tab target template" wording is replaced with the path rule (judged above). The release-owned content/docs/releases/v17/17-3.mdx it also names is read-only and still true (it records the pair co-constraint, not the value rule). No further change.
  • skills/objectstack-ui/evals case for newTabUrl — stays with the lane seat, as recorded on fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252.
  • Not Tier H — no governed path on the file list; the head is the thread's own same-repo branch claude/project-thread-ev6wfx, one commit on 7858c1d16; the PR is draft and lands through the queue once ready.

Implemented-by: claude/project-thread-ev6wfx
Reviewed-by: session_01WGufzgbJhFoXYsLSe5WSPP

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 (席位终稿)

席位意见:接受。契约复核第二轮 PASS(6472342134),第一轮 FAIL(6470108120)的条目已关闭。

席位核对 · ACCEPT · domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP · 2026-10-28T14:56Z

Head: 78e8f97515f54992d16469102d21f461840d42a0 · card #26924 (follow-up to PR #27252) · claim 6468072560 with its amendment

  1. 形态: a draft PR to main carrying Refs #26924, never Fixes, because fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252's merge closed the card. It has 9 paths (+62 / −8):

    • the D3 entry;
    • a new patch changeset for @objectstack/spec and @objectstack/platform-objects, carrying adr-0087: registered ui-action-new-tab-url-path-refused and a "Supersedes, in the same release" paragraph;
    • the form label in four locales, with regenerated translations;
    • one sentence in content/docs/protocol/objectui/actions.mdx.

    It is on the claim's branch claude/project-thread-ev6wfx. check-governed-merges --pr 27282 reports NOT governed. The assignee is os-support-ai.

  2. 前提实测: the review read 78e8f975, because the dev's docs commit cancelled the gate runs on 0c704ce9, which therefore has no verdicts. It found:

    • The D3 entry matches the SemanticMigration type and the 18.*-refused.ts precedent: surface, both remedies, "not mechanically convertible, structured TODO", reason and acceptanceCriteria. build-migration-registry.ts discovers it, and it is emitted as a TODO on every hop-18 replay.
    • The marker resolves to the entry.
    • The supersede paragraph is the only form available: both of fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose #27252's changesets are still unconsumed on main, and check-empty-changeset refuses editing a merge-base changeset.
    • No "URL template" label survives anywhere in the tree.
    • .describe(), the accept set, the authorable surface and the JSON schema are byte-unchanged.
  3. 门禁 check-runs (on this head): 54 runs, all completed at 2026-10-28T14:53Z as the review read them. All ten required contexts are success; Lint UI is skipped by its paths filter, and there is no failure or cancellation. Dev-side: 101 derived gates run, 0 NOT-MEASURED, 0 UNRUN. Spec 748 files and 22,349 tests, platform-objects 96 files and 1,361 tests, migrations and conversions 28 files and 945 tests; check:i18n OK.

  4. 接受的偏差:

  5. 范围外去向: none owed. The missing skills/objectstack-ui/evals case for newTabUrl (round 1 note) is left to the skills lane's own eval sweep. No card is filed, since the eval set covers taught behaviour by sampling and does not require one case per key.

  6. 落地条款: the seat checked the prose faces against the diff on this head: the PR body (r2, applied by the seat and read back), the changeset and its supersede paragraph, the D3 entry's text and the actions.mdx sentence. The seat readies the PR and arms auto-merge in this act, follows it to MERGED, and lands spec: ActionSchema.newTabUrl is a "path template" in its JSDoc but a "URL template" in its describe; an absolute value opens backend origin + the URL #26924.

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/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants