Repository navigation
fix(spec)!: an action's newTabUrl is a backend path starting with /; dashboard read-refusal and citation prose - #27252
Conversation
… north-star citations (WIP) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq
…tations Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzhJoJYPHSvBpq8euSq
📓 Docs Drift CheckThis PR changes 3 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 149 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 15167534024bd369a4807e9fe6b4d8408b198b59 && git checkout 15167534024bd369a4807e9fe6b4d8408b198b59
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9ff8154cf694c616de5d467b35af21e037de1509 7131b8152cad219d3cf15405d4a95d613864288a && git checkout -B drift-repro 9ff8154cf694c616de5d467b35af21e037de1509 && git merge --no-ff 7131b8152cad219d3cf15405d4a95d613864288a
node scripts/docs-audit/affected-docs.mjs --json 9ff8154cf694c616de5d467b35af21e037de1509
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: Triage seat, under the gap rule (#27062) condition (b): no record and no claim on this head after one round. The review runs at Generated by Claude Code |
Contract reviewServed-tier: Inputs: cards #26924, #27068, #26973 (bodies, triage, claims, the os-dev-report 6469626203), the PR body and file list, the net diff ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL One item: the ADR-0087 disposition on Generated by Claude Code |
Contract reviewServed-tier: Inputs read: card #26924 body and all 4 comments (triage 6454574648, claim 6468072560, amendment 6468134412, os-dev-report 6469626203); PR #27252 body, file list (26), net diff vs merge-base ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Must-fix: none. Should-fix: one, on the PR body's Implemented-by: VERDICT: PASS Generated by Claude Code |
Requested by Yibo Wang · project thread
Fixes #26924
Fixes #27068
Fixes #26973
Ruling-ref: none (seat choice A on #26924, per the card's options and triage 6454574648)
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: updated here —
skills/objectstack-ui/rules/actions.md(the oneskills/**sentence that taughtnewTabUrl, "For external deep-links … a direct URL template", now teaches a backend path and points an external page atopenIn); no eval underskills/objectstack-ui/evals/exercisesnewTabUrl(0 hits), so an eval case is follow-up owed if the seat wants one.skills/**carries no north-star citation (0 hits). Tier H: this PR editsskills/**.维护者速读
newTabUrl只能写本后端上以/开头的路径,绝对 URL 在解析处拒收,提示「写后端路径,外部页面改用type: 'url'+openIn: 'new-tab'」;JSDoc、describe、表单帮助(四种语言)和技能文档一处句子统一说法。另两项只改文字:仪表盘磁贴说明写明「非设计模式下,服务器以读权限拒绝(403)的数据集磁贴对该用户不显示,requiresObject仍只是安装门」;五处指向不存在的north-star.mdx章节的引用改指真实出处或删除。newTabUrl当路径拼在后端地址后面,写成https://…会打开坏标签页,而规范之前不拒收(spec:ActionSchema.newTabUrlis a "path template" in its JSDoc but a "URL template" in its describe; an absolute value opens backend origin + the URL #26924 选 A);fix(plugin-dashboard): leave a tile out when the server refuses its data to the viewer #27065 已上线磁贴隐藏行为但规范未说明(spec(ui): the dashboard widget describe does not say that a dataset tile the server refuses as a read refusal is left out for that viewer;requiresObjectkeeps its install-only meaning #27068);引用指向从未存在的章节(docs(spec, formula): five shipped citations point atcontent/docs/concepts/north-star.mdx"§8" / "§6.3", sections that page never had, and the path is renamed by #26073 #26973)。newTabUrl取值、0 处不以/开头。已存的sys_metadata行原样保存,不做转换(外部 URL 推不出后端路径):照常加载注册,启动计为 invalid 并打印[metadata_spec_invalid],读取带_diagnostics,下次保存被拒直到改成路径;渲染端不改。含skills/**,属 Tier H。回滚即 revert 本 PR。packages/spec/src/ui/action.zod.ts(newTabUrl的 regex 与提示)、packages/metadata-protocol/src/protocol.stored-conversions.test.ts(已存行处置)、skills/objectstack-ui/rules/actions.md。pnpm --filter @objectstack/spec exec vitest run src/ui/action-newtaburl-path.test.ts,预期 7 个用例全绿:https://与http://值、无前导/的值在newTabUrl处拒收并带外部页面的处方,/api/v1/invoice/{recordId}/pdf原样通过。skills/**(Tier H),席位 ACCEPT 后需要你批准。What changes
ActionSchema.newTabUrlis a "path template" in its JSDoc but a "URL template" in its describe; an absolute value opens backend origin + the URL #26924,@objectstack/spec(minor, BREAKING).ActionSchema.newTabUrlisz.string().regex(/^\//, …): a value that does not start with/is refused atnewTabUrlwith`newTabUrl` is a path on this backend and must start with `/` (e.g. `/sso-open/{recordId}`): the console prefixes the backend origin to it, so an absolute or relative URL opens a broken tab. To open an external page, use a `type: 'url'` action with `target: 'https://…'` (`${ctx.recordId}` where the record id goes) and `openIn: 'new-tab'`.A native string check, so it projects to the published JSON Schema as apatternand adds no dropped-refinement site (the spec build is unchanged on that ledger). The external-page remedy is measured, not assumed:type: 'url'is the action type whosetargettakes anhttp(s)URL, andopenIn: 'new-tab'is its new-tab switch (action.zod.tstarget/openIn); the test parses that exact shape. The JSDoc,.describe()and the action form's help text (action.form.ts) now all say "a path on this backend, starting with /", and the four*.metadata-forms.generated.tsbundles carry the new help text (en from source; zh-CN / ja-JP / es-ES hand-translated, keys unchanged). No renderer change:consoleServerAction.tsalready prefixes the backend origin.not-required (no-migration-prescription). No backend path can be derived from an external URL, so nothing converts a stored value. What load does to one, measured on this head (new pins inprotocol.stored-conversions.test.ts):loadMetaFromDb→{ loaded: 1, errors: 0, invalid: 1 }, the row registers with its storednewTabUrl, and boot prints[Protocol] [metadata_spec_invalid] stored action/export_invoice fails the current spec schema even after conversion (newTabUrl: …the prescription…). Registered anyway so it stays serveable and fixable …;getMetaItemserves the bytes unchanged with_diagnostics.valid: falselocatingnewTabUrl; a CONTROL row with a path loadsinvalid: 0. Save, measured with a one-off probe throughsaveMetaItem(deleted after use): the absolute value answers 422INVALID_METADATAaction/export_invoice failed spec validation: newTabUrl: …the prescription…; the same body with/export/{recordId}saves. The same sentence is in the JSDoc, the changeset and the disposition marker.liveness/action.jsonnewTabUrl: verdict unchanged (live), now with in-repo evidence (consoleServerAction.ts#createConsoleServerActionHandler) and a note stating the path rule.835b2d1c). EverynewTabUrlvalue written anywhere in the tree (CHANGELOGs excluded): 20 literal sites (packages/spectests 8,ui/tests 11,skills/**1), 0 that do not start with/;examples/**0,ui/examples/**0, no JSON fixture carries the key. The cloud SSO producers live inobjectstack-ai/cloud, which this session cannot read; the card names/sso-openas their endpoint, a path. A deployed database'ssys_metadatarows cannot be surveyed from here; an absolute value there behaves as measured above.requiresObjectkeeps its install-only meaning #27068,@objectstack/spec(prose only, no key). Measured onmainfirst:DashboardRenderer.tsx(refusedTiles, andrenderWidget'sif (!designMode && refusedTileKey && refusedTiles.has(refusedTileKey)) return null) leaves out a dataset-bound tile onceDatasetWidget'sonForbiddenfires, which it does only whenclassifyLoadError(e) === 'forbidden'(PR fix(plugin-dashboard): leave a tile out when the server refuses its data to the viewer #27065, merged as4aeb0543). TheDashboardWidgetSchemadocblock andrequiresObject's JSDoc and.describe()now say: outside design mode, a dataset-bound widget whose query the server refuses as a read refusal (403PERMISSION_DENIED) is left out for that viewer, with no key; in design mode it stays and shows its no-access state;requiresObject/requiresServicekeep their install-only meaning.liveness/dashboard.jsonrequiresObjectnote gains the same sentence. No accept-set change.content/docs/concepts/north-star.mdx"§8" / "§6.3", sections that page never had, and the path is renamed by #26073 #26973,@objectstack/spec+@objectstack/formula(prose only).content/docs/concepts/architecture-overview.mdx(the renamed page) has the headings Principles, Runtime Shape, Environment Artifact, Request Resolution, Non-Goals. Each citation:environment-artifact.zod.ts"§6.3 … runtime-inputs boundary" →architecture-overview.mdx§ "Environment Artifact", which states it ("It is not enough to deploy by itself; the host still supplies deployment config").expression.zod.ts,packages/formula/src/index.ts,packages/formula/README.md"§8 No private expression DSL" → no heading on the renamed page carries it; ADR-0032 does (its title: "… one CEL language …"), so the three citedocs/adr/0032-unified-expression-layer.md. This is the card's "the section that actually states the claim" rather than a bare drop; say if the seat wants the drop instead.hook-body.zod.ts"Metadata-only runtime" → no such heading anywhere; the pointer is dropped (its docblock already states the claim in full).git grep -n 'north-star.mdx' -- packages/→ 0 (positive control:git grep -n architecture -- packages/spec/srchits). The referencesshared/expression.mdxandsystem/environment-artifact.mdxare regenerated.skills/objectstack-ui/rules/actions.md(Tier H). The sentence "For external deep-links with no server round-trip, addnewTabUrl— a direct URL template" would steer an author into the refused value; it now says a backend path starting with/, and points an external URL atopenInabove. Readings: the file 204 → 204 lines, 2259 → 2258 tokens (ceiling 2259); the bundle (everySKILL.md) 4615 → 4615 lines; the whole shipped tree 215844 → 215843 tokens. Paid by deleting words in the same paragraph, no re-wrap credit.@objectstack/specminor (the narrowing, with the ADR-0087 marker; the dashboard and citation prose ride in it),@objectstack/formulapatch (README and docblock citation),@objectstack/platform-objectspatch (the four form-translation bundles).Tests
packages/spec/src/ui/action-newtaburl-path.test.ts(new, 7):https://andhttp://values and a relativesso-open/{recordId}are refused atnewTabUrl(invalid_format, one issue), the first sentence and thetype: 'url'/openIn: 'new-tab'prescription asserted; refused through the registeredactionmetadata schema too;/api/v1/invoice/{recordId}/pdfparses byte-identically; a query-string path passes; the remedy shape (type: 'url',https://target,openIn: 'new-tab') parses.packages/metadata-protocol/src/protocol.stored-conversions.test.ts(3 new): the stored absolute row is served unchanged with_diagnosticsnamingnewTabUrl; boot counts itinvalid: 1and still registers it with the stored value; CONTROL: the same row with a path loads valid.scripts/ablation-replace.mjsturned the rule intoregex(/^/, …)→ the 4 refusal pins red, the 3 acceptance pins green; restored, blob == HEAD andgit diff HEADempty.Acceptance notes
//hostand/\hoststart with/and pass. The console concatenates them after the backend origin, so they open a path on the backend, not another origin; refusing them was not asked and would be a wider narrowing.{recordId}(String.replacewith a string pattern). No value in the tree repeats the placeholder; not changed here (no renderer change by order).newTabUrllabel stays "New-tab URL" (zh-CN "新标签页 URL 模板"); only the help text moved.content/docs/protocol/objectui/actions.mdxcallsnewTabUrl"a zero-roundtrip new-tab target template"; still true under the rule, so left alone.Verification
All readings on a shared container, base
maind745bb5e5after merging it (no overlap with this diff); the lock's VERDICT lines are quoted.Tests and generated artifacts (head 7131b81)
pnpm --filter @objectstack/spec build→ VERDICT command-exit 0 (dropped-refinements ledger unchanged; the publishedui/Action.jsonnow carriesnewTabUrl: { type: string, pattern: '^\/' }).pnpm --filter @objectstack/spec check:generated --fix→ onlycheck:docswas stale (6 reference pages regenerated, committed); VERDICT command-exit 0.pnpm --filter @objectstack/spec test→ Test Files 744 passed (744), Tests 22270 passed | 1 todo;typecheck→ exit 0.pnpm --filter '@objectstack/metadata-protocol^...' buildthenpnpm --filter @objectstack/metadata-protocol test→ Test Files 277 passed | 4 skipped (281), Tests 21753 passed | 26 skipped.pnpm --filter @objectstack/formula test→ Test Files 57 passed (57), Tests 1522 passed;typecheck→ exit 0.pnpm --filter @objectstack/platform-objects test→ Test Files 96 passed (96), Tests 1361 passed.action-newtaburl-path.test.ts→ 7 passed;protocol.stored-conversions.test.ts→ 19 passed (the boot line quoted under What changes is this run's stderr).node scripts/check-skills-token-ratchet.mjs→skills/objectstack-ui/rules/actions.md is 2258 tokens (ceiling 2259; headroom 1).Full dogfood (head 7131b81)
pnpm --filter '@objectstack/dogfood^...' buildthenpnpm --filter @objectstack/dogfood test(no shard, no path), one locked run → VERDICT command-exit 0 · held the lock 1961s. Test Files 348 passed | 3 skipped (351), Tests 2804 passed | 20 skipped. No failure.Derived gates (head 7131b81)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 115 families (26 paths, 288 changed lines vs merge based745bb5e5).spec check:skill-examplesandcheck:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET: noclient-react/ plugin dists). After building those dists (VERDICT command-exit 0 · 280s):check:skill-examplesexit 0 (270 prose examples type-check across 3 surfaces),check:dual-build-cjs-loadsexit 0 (112 entry points across 69 packages load).dispatch-gates --ran→115 derived famil(ies) accounted for — 115 run, 0 NOT-MEASURED, exit 0.GITHUB_EVENT_NAME=pull_request node scripts/check-changeset-no-major.mjs --base origin/main --event FILE(this body) → exit 0.Generated by Claude Code