Skip to content

fix(data-objectstack): ObjectStackAdapter reads baseUrl as the server origin, so a base ending in /api/vN reaches discovery; the objectui skill teaches the origin - #27967

Merged
os-zhuang merged 1 commit into
mainfrom
claude/project-thread-83w07l
Oct 30, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
claude/project-thread-83w07l

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread
Fixes #27774

维护者速读

  • 改了什么:ObjectStackAdapter 构造时把 baseUrl 一次归一为服务器 origin(去掉尾部 / 与尾部 /api/vN),发现请求、内嵌 ObjectStackClient 的路由与适配器各处裸读共用这一个值;原来五处逐调用的后缀判断删掉。objectui 技能的两份指南与包 README 改教 origin 形式(同源代理写 ''),data-integration eval 拒绝旧写法 baseUrl: '/api/v1'。
  • 为什么改:技能教的 baseUrl: '/api/v1' 让发现请求打到 /api/v1/api/v1/discovery,connect() 失败,之后每次读写随之失败。适配器自己的搜索/数据/导出/元数据裸读早已去掉该后缀,但发现请求与内嵌 client 没有;只改发现一行,client 路由的读(如 /api/v1/meta/:type)仍会翻倍,所以归一放在构造处。
  • 风险与回滚:URL 拼装只在 @object-ui/data-objectstack 一个包变化;origin 形式的 URL 字节不变(全包 104 个测试文件 1228 用例绿);回滚即 revert 本 PR,无数据迁移,无 spec 改动。
  • 先看:ui/packages/data-objectstack/src/index.ts 的 serverOriginOf 与构造函数 hunk;skills/objectui/guides/data-integration.md;skills/objectui/evals/data-integration.json。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/data-objectstack/src/connectBaseUrlApiSuffix-27774.test.ts,预期 7 passed;同一文件对 origin/main 跑是 4 failed(断言里出现 /api/v1/api/v1/discovery)。
  • 需要你做的:Tier H(skills/**)—— 席位 ACCEPT 与合约复核 PASS 之后,由你合并或入队(或 os-zhuang / hotlong 的授权批准后由席位落地)。

What changes

Adapter (@object-ui/data-objectstack)

  • ObjectStackAdapter reads baseUrl as the server origin ONCE, where it and its ObjectStackClient are built (serverOriginOf): a trailing slash and a trailing /api/vN are dropped. '', http://host:3000, /api/v1 and http://host:3000/api/v1 now all reach /api/v1/discovery and the same /api/v1/... routes, through the client and through the adapter's raw reads alike.
  • The five per-call hasApiVersionSuffix checks (search, data, export, two meta reads) are gone; each site joins /api/v1/... onto the normalised base. The analytics dataset query and the approvals reads, which joined onto the base as given, are covered by the same normalisation without a change of their own.
  • baseUrl on the constructor options carries a doc comment naming the form.
  • New pin connectBaseUrlApiSuffix-27774.test.ts: the discovery URL the injected fetch receives for the origin, the empty same-origin base, a relative and an absolute base ending in /api/v1, a trailing slash after it, a non-version path prefix (kept), and one client-routed read (listMetadataItems) from an /api/v1 base that must reach /api/v1/meta/position once.

Skill (skills/objectui, governed, Tier H) and README

  • guides/data-integration.md: the four adapter examples teach baseUrl: '' (the first one names the two forms: '' behind a same-origin proxy, or the server origin); the constructor-options comment says the adapter adds /api/v1.
  • guides/auth-permissions.md: the one adapter example teaches baseUrl: ''. This file was not on the claim's declared surface; it is the fifth site of the same defect and the card's acceptance grep (git grep -n "baseUrl: '/api/v1'" -- skills is 0 hits) cannot pass without it.
  • evals/data-integration.json: eval 2 (building the frontend before the backend) expects the adapter to be handed the origin and refuses an answer containing baseUrl: '/api/v1 (the wrong form's prefix, so the acceptance grep stays at 0 hits).
  • ui/packages/data-objectstack/README.md: the one ObjectStackAdapter example that taught /api/v1 now teaches ''. Its MetadataClient examples keep /api/v1: that class already collapses the suffix itself.

Changeset: .changeset/27774-adapter-base-url-origin.md, patch on @object-ui/data-objectstack and @objectstack/skills.

Why

A customer agent that loads the published objectui skill and wires a real backend was told to write new ObjectStackAdapter({ baseUrl: '/api/v1' }). Discovery joined /api/v1/discovery onto the base as given, so the first request asked for /api/v1/api/v1/discovery, connect() failed, and every read and write that awaits it failed with it. The triage ruled platform first: the adapter accepts both spellings (its sibling reads already did), pinned, and the skill then teaches the origin form ObjectStackClient takes.

Mechanism assumption re-measured. The order assumed a strip on the discovery line alone, like the four sibling reads. Read from source: the adapter spreads its whole config into new ObjectStackClient({ ...config }), and the client joins every conventional route (/api/v1/meta, /api/v1/data, ...) onto ITS base as given, so a discovery-only strip would have connected and then doubled every client-routed read. The normalisation therefore sits in the constructor, before the client is built, and the pin's last case reads a client-routed URL. Same ruling, one step earlier.

Tests

Package tests and type-check, under the shared verify lock, on e73e6d45:

pnpm --dir ui exec vitest run packages/data-objectstack/ — Test Files 104 passed (104) · Tests 1228 passed (1228) · exit 0
 Test Files  104 passed (104)
      Tests  1228 passed (1228)
pnpm --filter @object-ui/data-objectstack run type-check — exit 0
> @object-ui/data-objectstack@18.2.0 type-check
> tsc --noEmit

Red on the pre-fix adapter, green on the fix (one leg each, the pre-fix index.ts restored from the merge base ed0e71db and put back with git checkout HEAD -- …; blob hash equal before and after, git diff HEAD empty):

pre-fix: Tests 4 failed | 3 passed (7)
     × connects from a relative base ending in /api/v1
     × connects from an absolute base ending in /api/v1
     × connects from a base ending in /api/v1 and a trailing slash
     × routes a client read through the same base once, not /api/v1/api/v1
AssertionError: expected [ '/api/v1/api/v1/discovery' ] to deeply equal [ '/api/v1/discovery' ]
 Test Files  1 failed (1)
      Tests  4 failed | 3 passed (7)
fix: Tests 7 passed (7)
 Test Files  1 passed (1)
      Tests  7 passed (7)

The three origin-form cases pass on both trees: the origin spelling's bytes do not change.

Acceptance notes

  • git grep -n "baseUrl: '/api/v1'" -- skills → 0 hits on this head (the card's acceptance line).
  • Skill size, two readings, lines and ratchet tokens (ceil(utf8 bytes / 4), the convention check-skills-token-ratchet.mjs prints):
    • guides/data-integration.md: 480 → 480 lines · 4303 → 4328 tokens (ceiling 4451).
    • guides/auth-permissions.md: 416 → 416 lines · 3866 → 3864 tokens (ceiling 3866, headroom was 0; a comment there was dropped to stay under it).
    • evals/data-integration.json: 54 → 55 lines · 466 → 470 tokens (ceiling 471; the new refusal string is paid for by a shorter expected_output in the same file).
    • Whole skills/objectui bundle: 5416 → 5417 lines. Whole published catalog, the ratchet's "ratcheted (authored)" total: 208049 → 208079 tokens of 217809. No ceiling moved.
  • baseUrl ending in /api/v2 is read as the origin too (the siblings' regex, kept); every route is then /api/v1/.... Not pinned: there is no v2 API to teach.
  • The shared discovery cache is keyed by the normalised origin, so '/api/v1' and '' on one page share one discovery read, as they should.
  • Not changed, noted: skills/objectui/guides/project-setup.md teaches VITE_API_BASE_URL=/api/v1 as an env example without wiring it into the adapter; with this PR that value would work either way. MetadataClient (same package) already collapses a /api/v1 base; its README examples are left as they are. Neither is a card.
  • Seat 4's regions in index.ts (field-access fields, dropInflightReads, getRecordFieldAccess) are untouched; PR perf(console): opening a record re-reads no list, asks its field access once, and the bell polls once a minute #27907's file list does not include this file.

Verification

  • Derived gates (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, off the merge base, head e73e6d45): 58 derived, 57 run with exit 0 (node scripts/check-sdui-manifest.mjs and node scripts/check-skills-token-ratchet.mjs among them; check:skill-examples first refused on a missing client-react dist and passed after that package was built), 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exits 3 here (it reads every package's dist, and this worktree holds only the adapter's dependency closure) and is declared to CI's Lint & Repo Gates. --ran reconciliation: 58 accounted, 0 UNRUN; each exit code is in the os-dev-report comment on skills/objectui teaches ObjectStackAdapter({ baseUrl: '/api/v1' }), which sends discovery to /api/v1/api/v1/discovery, so connect() fails and every adapter call with it #27774.
  • pnpm --filter @object-ui/data-objectstack lint exit 0 (0 errors; the warnings it prints are pre-existing anys, none on a changed line). pnpm --dir ui run check:new-line-citations: 0 new citations.
  • Changed lines vs the merge base: 186 (+143 / −43), under the 3000 human-merge threshold.

Clause-②: no
Metadata-protocol: none

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; package lint run (see Verification).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a, not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the maintainer digest the dispatch asks for, in Chinese by rule).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…ases reach discovery; objectui skill teaches the origin

The adapter normalises `baseUrl` once where it and its ObjectStackClient are
built: a trailing slash and a trailing `/api/vN` are dropped, so `''`, an
origin, `/api/v1` and `origin/api/v1` all reach `/api/v1/discovery` and the
same `/api/v1/...` routes through the client and the raw reads alike. The
five per-call suffix checks are replaced by that one helper.

skills/objectui (data-integration, auth-permissions) and the package README
teach the origin form the console passes; the data-integration eval refuses
an answer that writes the old form.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e73e6d45bc6bf715a2cf91f7ec5bdf7aed92b247
Local-runs: none
Scope: skills/objectui/evals/data-integration.json, skills/objectui/guides/auth-permissions.md, skills/objectui/guides/data-integration.md + ui/packages/data-objectstack/src/index.ts, packages/client/src/index.ts, ui/apps/console/src/App.tsx, ui/packages/data-objectstack/src/metadata-client.ts, ui/packages/auth/src/createAuthenticatedFetch.ts, ui/packages/data-objectstack/src/connectBaseUrlApiSuffix-27774.test.ts, ui/scripts/check-skill-eval-tokens.mjs

Inputs: card #27774 (body, triage 6510453993, claim 6515328969, dev report 6515829241), PR #27967 body and file list (7 files, +143/−43), the net diff origin/main...e73e6d45 (merge base ed0e71db), and the 54 check-runs on the head. The rest of the diff (.changeset/27774-adapter-base-url-origin.md, ui/packages/data-objectstack/README.md, the new pin, index.ts) is the lane seat's ACCEPT at TIER_DEFAULT; index.ts and the pin were READ here to judge the skill, never reviewed.

Check-runs on the head, read at 2026-10-30T05:49Z: 54 total — 32 success, 8 skipped, 13 in progress (Test UI 1/3/5/6/7/9/10/11/12/13/16 of 16, Test Core 1/8, Type Check UI), 1 failure: Test UI (2/16). Its annotation names ui/packages/types/src/__tests__/column-width-config-retired-10582.test.ts:113 failing with ENOENT … ui/packages/cli/tsup.config.bundled_f5hi2m4wg4d.mjs — a tree walk that stat'ed a tsup temp file another package's build deleted under it. No path of this diff is under ui/packages/types or ui/packages/cli; the failure is a race in the shard, not this change, and a rerun is owed before landing. The governed gates that answer this review's families are green: Governed Surface Queue Guard, Check Changeset, Lint · docs & code guards, Lint · ESLint, Lint · PM dispatch-gates, Type Check · source/consumer/workspace/debt, TypeScript Type Check, Build UI Deps, Live E2E UI. The landing precondition "every check green" is NOT met at this reading; this record judges the design, and the seat re-reads the head before it lands.

① Derived judgments

  1. ObjectStackAdapter({ baseUrl }) now reads a value ending in /api/vN (and a trailing slash) as the server origin (serverOriginOf, index.ts:948), once, before the wrapped ObjectStackClient is built — the tolerance arm of the card's two options, which is the arm the triage ruled ("strips a trailing /api/vN the same way its search, data, export and meta paths already do, so both spellings work. Pin both spellings"). Right. The accept set the package DECLARED did not move: its README and four of its own reads already accepted that spelling on main; the two sites that disagreed (discovery, the client) now agree with them.
  2. The client and RealtimeAPI receive the origin, not the raw value. ObjectStackClient only strips a trailing slash (packages/client/src/index.ts:1704) and joins absolute /api/v1/... routes onto its base (:1733, :1854 ff.), so a discovery-only strip would have connected and then doubled every client-routed read; the constructor placement is the right one and the pin's last case (listMetadataItems → /api/v1/meta/position once) reads exactly that. Right — the PR's "mechanism assumption re-measured" is confirmed from source.
  3. The five per-call hasApiVersionSuffix branches are gone; every raw read joins /api/v1/... onto the origin. For the origin form the URL bytes are identical to main; for the suffixed form they are now the same URLs. The approvals reads (:7297, :7322), the analytics query (:6875) and the two unversioned upload sites (:7165, :7227, /api/data/.../upload, pre-existing shape, not reviewed) join onto the same value. Right.
  4. The shared discovery cache is keyed by the origin (getSharedDiscovery(baseUrl), the empty origin keyed by a fixed default token), so '' and '/api/v1' on one page share one read. Right.
  5. The skill's taught form moves from '/api/v1' to '' at all five adapter sites (data-integration.md :133 with the two forms named, :155, :207 constructor comment, :218, :462; auth-permissions.md :136), and evals/data-integration.json eval 2 refuses baseUrl: '/api/v1. git grep -n "baseUrl: '/api/v1'" -- skills is 0 hits on the head — the card's acceptance line. Right.
  6. No public type surface moves: the constructor option stays baseUrl: string (a doc comment is added); no packages/spec, no governed-register row, no ui/ type the Console compiles against changes. Type Check UI is still running; Type Check · workspace and TypeScript Type Check are green.
  7. Left standing, by the dev's choice and agreed here: MetadataClient keeps its own suffix strip (metadata-client.ts:812) and its README examples; project-setup.md:324 keeps VITE_API_BASE_URL=/api/v1 as an unwired env example. Both work on this tree.

② Semver level

.changeset/27774-adapter-base-url-origin.md: @object-ui/data-objectstack: patch, @objectstack/skills: patch; Clause-②: no; Metadata-protocol: none; the PR body carries the same two lines. Matches the diff: a bug fix inside an accept set the package already declared (README, four sibling reads), no new export, no changed option type, no metadata-author surface. Clause-②: no holds — had the triage picked the card's other arm (refuse a /api/vN base with a named error) this would have been yes (narrowing) and BREAKING; it did not. @objectstack/skills publishes the skill text, so a changeset is owed there too and skip-changeset would have been wrong. Check Changeset is green. Right.

③ Boundary flags

  • open_questions[0] — report comment first line (os-dev.md says the bare os-dev-report marker is line 1; post-stamped.mjs refuses a body whose first non-empty line is not the seat signature). Read both: .claude/agents/os-dev.md:17 does say 首行单独一行是纯文本 os-dev-report; scripts/pm/post-stamped.mjs:468 ("The first line is the seat's signature, or the body is refused") does refuse it. The conflict is real. A (marker on line 2) was right for this PR: the readers skip one signature line (stripSignatureLine), and this brief located the report by its SECOND line. B is escalated, not answered here: .claude/agents/os-dev.md is a governed .claude/** path (Tier S) and outside this card; the next seat that touches the playbook amends the clause to "signature first, marker second". Not blocking.
  • out_of_scope_findings[3] — declared-surface deviation, guides/auth-permissions.md edited (one line). Accepted: the card's acceptance is a grep over skills, this is its fifth hit, the same governed bundle and tier, and no other claim holds the file. It is in hits and judged in ④.
  • out_of_scope_findings[0] — project-setup.md:324 VITE_API_BASE_URL=/api/v1. Agreed, not a defect: the value is an env example, never wired to the adapter, and a reader who does wire it gets the spelling the adapter now tolerates. No card.
  • out_of_scope_findings[1] — README MetadataClient examples keep '/api/v1'. Agreed: a different class with its own strip (metadata-client.ts:812-813). No card.
  • out_of_scope_findings[2] — approvals and analytics reads. Agreed from source (index.ts:6875, :7297, :7322): they join onto the normalised origin now; the analytics local trim was dead code after the constructor change.
  • Added by this review, same class as the README note: ui/packages/auth/src/createAuthenticatedFetch.ts:76-78, the function's own JSDoc example, still teaches new ObjectStackAdapter({ baseUrl: '/api/v1', fetch: authenticatedFetch }). It works on this tree by the same tolerance; it is a doc comment outside the skill, outside this PR's claim, and not a defect. Noted for the next edit of that file; no card.
  • Not a dev flag — the check-run reading above: Test UI (2/16) red on an unrelated tsup temp-file race; rerun before landing.

④ Design judgment

skills/objectui/guides/data-integration.md — five hunks, judged against the card's eval prompt and the adapter on main plus this PR.

  • baseUrl: '' is right in every snippet's context. On main '' already reached /api/v1/discovery (discovery branch baseUrl ? … : '/api/v1/discovery') and every client route (''.replace(/\/$/, '') → '', then /api/v1/...); on this PR serverOriginOf('') → '' and the same URLs. Nothing the guide taught that worked stops working; the one thing that did not (client-routed reads from '/api/v1') now does, and the guide no longer teaches it.
  • The auth snippet (:151-157): createAuthClient({ baseURL: '/api/v1/auth' }) beside baseUrl: '' is consistent — both are same-origin spellings, and that is the shape the Console itself passes: App.tsx:105-106 hands the adapter VITE_SERVER_URL || '' and builds the auth URL as ${SERVER_URL}/api/v1/auth, so the two values move together. A non-proxied deployment writes baseUrl: 'https://api.example.com' and baseURL: 'https://api.example.com/api/v1/auth'; the first snippet's comment (:133, "the server ORIGIN: '' behind a same-origin proxy, or 'https://api.example.com'") and the constructor-options comment (:207, "Server origin ('' = same origin); the adapter adds /api/v1") name that form, and "the adapter adds /api/v1" is true of every URL it builds (discovery, search, data, export, meta, analytics, approvals) and of the client's routes.
  • should-fix (one voice), not blocking: the os:check "Test DataSource helper" at :388 keeps a third spelling, new ObjectStackAdapter({ baseUrl: '/' }). It works on both trees (the client and serverOriginOf each strip the slash to ''), so it is not a defect, but the guide now explains '' and the origin and leaves '/' unexplained two sections later; the next edit of this file makes it ''. Token headroom is 123 (4328/4451).
  • Observation outside this PR's hunk: the same auth snippet calls createAuthenticatedFetch(authClient) while the function takes an options object (createAuthenticatedFetch(options?: CreateAuthenticatedFetchOptions), createAuthenticatedFetch.ts:82); auth-permissions.md:133 calls it with no argument. Pre-existing on main, not this PR's claim, and the block is not an os:check fence so no gate reads it; a card for the domain:skills lane if it wants it, not a finding here.

skills/objectui/guides/auth-permissions.md — one hunk (:136). Holds: the surrounding section is same-origin throughout (createAuthClient({ baseURL: '/api/v1/auth' }) at :25 and :132, authUrl="/api/v1/auth" at :29), so '' is the matching adapter value, and the Console passes exactly this pair. The explanatory comment that data-integration.md:133 carries was dropped here to stay under the file's ratchet ceiling (3864 of 3866, headroom was 0); acceptable because the reader loads the bundle and SKILL.md routes data wiring to data-integration.md, where the two forms are named twice. Accepted, no finding.

skills/objectui/evals/data-integration.json — eval 2.

  • must_not_contain: "baseUrl: '/api/v1" holds and avoids false positives. It refuses baseUrl: '/api/v1' and baseUrl: '/api/v1/' (the prefix is deliberate, so the acceptance grep's shape is refused with any tail). Under the substring, case-sensitive match the repo's eval gates describe (check-skill-eval-tokens.mjs, "accidental substring"), it does NOT match createAuthClient({ baseURL: '/api/v1/auth' }) (differs at URL/Url), the Vite proxy block ('/api': { target: … }), baseUrl: '', or baseUrl: 'http://localhost:3000'. No guide in the bundle spells the string at this head (grep: 0 hits), so an answer that follows the skill is never refused for restating a rule it follows — the objectui#7370 pitfall the eval-tokens gate documents is avoided — and the expected_output does not spell it either (the 27455 self-agreement). Holes, not false positives: a double-quoted "/api/v1" or an absolute 'http://localhost:3000/api/v1' pass the refusal; the first is not a form the skill's single-quoted examples invite, the second now works. Acceptable.
  • The rewritten expected_output ("Runs the server with os dev (Todo in objectstack.config), proxies /api from Vite, hands ObjectStackAdapter the origin ''; no MSW.") still matches its prompt ("build my ObjectUI frontend before the backend is deployed … how do I get a working API locally?"): the guide's "Frontend-first development" section (:353-370) teaches exactly os dev plus the Vite /api proxy, and the adapter wiring on the same origin is the half of that answer the eval now checks. must_contain (os dev, proxy, objectstack.config) is unchanged and every token is taught; the eval-tokens gate is in the green Lint · docs & code guards family.
  • Budget: 470 of 471 ratchet tokens — one token of headroom; the next edit to this file pays in the same file.

Tried to break the design (cited code, read not reviewed): serverOriginOf strips ONE trailing slash then /api/vN; '/api/v1//' would survive as /api/v1/ and double — not a taught form, and the client strips one slash too. A server really mounted under a /api/v1 path prefix can no longer be addressed by that prefix — the four sibling reads already made that choice on main, and the skill teaches the origin. /api/v2 is read as the origin and routed to /api/v1/... — the siblings' regex, no v2 API exists; the dev's note is right. The pin's fake fetch answers any URL ending in /api/v1/discovery, so on the red leg connect() resolved and the URL assertion was the failing one — a looser fake than it could be, but the pin reads the URL the fetch received, which is the claim. No must-fix.

Findings: should-fix ×1 (data-integration.md:388 '/' spelling). Must-fix: none.

Implemented-by: claude/project-thread-83w07l
Reviewed-by: session_01Utp6CaJoinvGQSbHeDaWBe

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

维护者速读

  • 改了什么:ObjectStackAdapter 在构造时把 baseUrl 统一归一成服务器源(去掉末尾的 / 和 /api/vN)。内嵌的 ObjectStackClient 和适配器自己的各个裸请求共用这个源,五处各自判断后缀的代码删掉了。同时,objectui skill 和包 README 改为教源写法:同源代理下写 '',否则写服务器地址。
  • 为什么改:skill 一直教 baseUrl: '/api/v1',按这个写,发现请求会变成 /api/v1/api/v1/discovery,connect() 失败,之后所有读写跟着失败(skills/objectui teaches ObjectStackAdapter({ baseUrl: '/api/v1' }), which sends discovery to /api/v1/api/v1/discovery, so connect() fails and every adapter call with it #27774)。分诊裁定平台优先:两种写法都要能用。
  • 风险与回滚:只有带 /api/vN 后缀的 baseUrl 行为会变(从坏的变成能用);'' 和源写法的 URL 一个字节都不变,有 7 条钉子测试覆盖。回滚就是 revert 本 PR。
  • 先看:ui/packages/data-objectstack/src/index.ts 里的 serverOriginOf 和构造函数;skills/objectui/guides/data-integration.md 的四处改动。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/data-objectstack/src/connectBaseUrlApiSuffix-27774.test.ts,预期 7 条全部通过(在 main 上是 4 红 3 绿)。
  • 需要你做的:Tier H(skills/**),请审阅并批准。合约复核 PASS 见评论 6515998431,Head-sha 为 e73e6d45bc6bf715a2cf91f7ec5bdf7aed92b247。

席位意见:建议批准。dev 实测证伪了「只改发现那一行」的假设:client 也会让路由翻倍,所以把归一提前到构造函数,这和分诊的裁定是同一个方向。合约复核留了一条非阻塞的 should-fix:指南第 388 行还写着 baseUrl: '/',这个写法能用,等这个文件下次修改时顺手改掉。

席位核对

ACCEPT · head e73e6d45bc6bf715a2cf91f7ec5bdf7aed92b247 · 2026-10-30T06:18Z

  • Shape: draft → main, Fixes #27774. Clause-②: no (a bug fix inside an accept set the package already declared) and Metadata-protocol: none match the claim. Changeset: patch for @object-ui/data-objectstack and @objectstack/skills.
  • Ruling (triage 6510453993): platform first, one PR, cross-domain exception, Tier H. The adapter accepts both spellings and the skill teaches the origin form. Both are done. The serial predecessor PR docs(skills): the objectui data-integration guide stops teaching the MSW plugin removed in 12.0 #27764 merged first.
  • Premise, measured: the order assumed a one-line fix in discovery. The dev disproved it: ObjectStackClient joins /api/v1/... onto its base as given, so the client's routes double as well. The normalisation therefore sits once in the constructor (serverOriginOf), shared by the client and every raw read, and the five per-call suffix checks are gone. The adapter's baseUrl field is private, and MetadataClient gets the normalised value too.
  • Tests: connectBaseUrlApiSuffix-27774 is 4 red / 3 green on main and 7 green on the head. The whole package passes (104 files, 1228 tests), and type-check exits 0.
  • Contract review: PASS on this head, comment 6515998431 (CONTRACT_REVIEW_TIER, scope = the three skills/** hits + cited code). It has no must-fix. One should-fix is left for the file's next edit: guide :388 baseUrl: '/', which works.
  • Gates (check-runs on the head): 48 success, 8 skipped, 0 failure. Test UI (2/16) was a tsup temp-file ENOENT race unrelated to the diff; it was re-run once (run 41009153397, attempt 2) and passed. mergeable_state clean. check-governed-merges --pr 27967: GOVERNED, skills/** ×3, Tier H, 186 changed lines.
  • Accepted deviations:
    • guides/auth-permissions.md, the same defect and the fifth acceptance-grep hit, is outside the declared surface.
    • The package README was taught the same way.
    • The report's signature-first line follows post-stamped, as noted in its open question.
  • Out of scope: the createAuthenticatedFetch JSDoc still shows /api/v1, which works after this PR. The guide's createAuthenticatedFetch(authClient) call shape predates this PR.
  • Landing: Tier H. The seat requests review from os-zhuang and hotlong and labels the PR needs-user-decision. ⛔ The seat does not mark it ready or arm auto-merge. An authorized approval, or a maintainer merge, is the review record; after an approval the seat lands it.

Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 30, 2026 07:08
@os-zhuang
os-zhuang enabled auto-merge October 30, 2026 07:09
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 8ebdd28 Oct 30, 2026
94 of 96 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-83w07l branch October 30, 2026 07:31
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 size/m tests tooling

Projects

None yet

2 participants