Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

skills/objectui/guides/i18n.md 两处把 label 规则归给 @objectstack/spec v4,仓内实测 ^17.0.0 —— agent 面上 13 个 major 的版本化石 #5081

Closed

Description

@yinlianghui

PM 状态(2026-08-17,session_01GTRjn8xBqp75dk7kFupVRt):pm:blocked
Blocked-by: #5084
修本卡必须同改 KNOWN_CLAIMS 里指向本卡的 stale 条目,而该条目由 PR #5084(#4981 门禁扩面)携带、尚未落 main(PR #5084 又 Blocked-by PR #5080 待维护者亲合)。先修会让 PR #5084 落地即触发向下棘轮红。PR #5084 MERGED 后本卡解锁回队,修复 PR 同改该 stale 条目(方向倾向卡面 2:删版本限定语)。

越界发现,记录于 #4981(把 skills/ 纳入 doc-version-claims 扫描面)实施期间 —— 扩面后的扫描在 skills/ 上共命中 12 条版本字面量,卡面列了其中 4 条脚手架化石,这是卡面未列的第 5、6 条(同一条 claim 的两次出现)。#4981 的完成范围是那 4 处字面量 + 门禁扩面,这一条的修法是另一个判断(改数字 vs 删版本限定语),故只记录、不在那两个 PR 内动手。处置同 #3697 的普查先例:先在 KNOWN_CLAIMS 里以 kind: 'stale' 记账(记录债务而不是祝福它),修复另开卡 —— #3708 / #3709 / #3710 / #3690 就是那 9 条 stale 的还债卡。

事实(对 origin/main @ 6098ecd08 实测)

位置 原文 仓内实测
skills/objectui/guides/i18n.md:117 In Object UI schemas, use plain string labels (per `@objectstack/spec` v4). @objectstack/spec ^17.0.0,33 处声明(20 dependencies / 12 devDependencies / 1 peerDependencies),node_modules 装的是 17.0.0
skills/objectui/guides/i18n.md:162 Using {key, defaultValue} translation objects — `@objectstack/spec` v4 uses plain strings only. 同上

13 个 major。与 #3708 修掉的那条完全同形 —— architecture-overview.md 的分层图里写 @objectstack/spec ^4.0.4,当时每个清单都已经是 ^17.0.0-rc.5;区别只在受害面:那条是人读的 content/docs,这条在给 agent 读的 skills/ 上。

为什么不是「无害的旧文案」

两句话都在用版本号给规则背书(「按 spec v4,label 是纯字符串」)。规则本身今天是否仍成立,是需要对 spec 17 复核的第二个问题 —— 而读者(人或 AI)拿到的第一手信息是「这条规则的依据是 v4」,它会把一个 13 个 major 前的版本号当成当前契约去引用、去搜文档、去写注释。#4611 正是这一族的另一面(ui-action.ts 的注释声称「In spec 17 I18nLabelSchema is z.ZodString」,对着实装的 rc.6 是假的,差点让一个席位实现一个 no-op)。

修法(两个方向,交 PM 定)

  1. 改成实测版本(v17)—— 前提是先对 spec 17 复核「plain string labels」这条规则今天仍然成立;若已变,那就不是版本号的问题而是规则本身要重写。
  2. 删掉版本限定语,写成「per @objectstack/spec」/「the spec uses plain strings only」—— PR docs(packages): 退役 36 个包 README 里已死的 release-metadata §Compatibility 生成块 #3688 / docs(cli): 兼容表两行失真 —— Node 行改指根 engines,删掉 CLI 并不依赖的 spec 兼容行 #3698 对 36 个 README 和 CLI 页做过的那一手,也是 doc-version-claims 的 SCAN_ROOTS 不含 skills/:agent 面的 4 处版本字面量从未被任何门禁读过(实测 1–2 个 major 化石) #4981 卡面列的方向 2。这一种不会再化石化。

倾向 2:这两句想说的是「当前契约是纯字符串」,版本号在这里不承载信息,只承载会过期的风险。

与 #4981 的关系

不是重复,也不是 Blocked-by。#4981 的门禁 PR 会把这条 claim 以 kind: 'stale' 收进 KNOWN_CLAIMS(带指向本卡的理由),所以:

  • 这条化石从此有门禁看着(在此之前 skills/ 完全在扫描面之外);
  • 本卡修好之后,那条 stale 条目必须在同一个改动里删掉或改类,否则 doc-version-claims 的向下棘轮会红(「KNOWN_CLAIMS names version claims that are no longer in the tree」)—— 这正是那份清单不会烂掉的机制。

Activity

  1. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    ContributorAuthor

    (正文末尾的署名被 issues 端点剥掉了,补在这里。)


    Generated by Claude Code

  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Unlock scan (objectstack-wide triage seat, fire ~19:47Z 2026-08-18): blocker PR #5084 MERGED 2026-08-18 15:59Z — the doc-version-claims ledger now records the i18n.md v4 attributions as stale entries. pm:blocked removed (card keeps pm:queue). The fix fork that PR deliberately left to this card — rewrite "v4" → "v17" vs. drop the version qualifier — is this card's own judgment; whichever is chosen, flip/remove the stale ledger entry in the same PR so the down-ratchet ("no entry may outlive the claim it excuses") stays green.


    Generated by Claude Code

  3. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round 19
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5081-i18n-spec-version-fossil
    Worktree: objectui-issue-5081
    Domain: repo:objectui (execution seat)
    File surface: skills/objectui/guides/i18n.md (lines 117 and 162) and the KNOWN_CLAIMS entry in scripts/__tests__/doc-version-claims.test.ts (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — no path-derived mandate; tier is this seat's call.
    Clause-②: no — documentation wording plus a ledger entry deletion.
    Serial constraints cleared: skills/objectui/guides/i18n.md has no in-flight claim. PR5371 (#5126) is awaiting maintainer merge on schema-expressions.md, data-integration.md and rules/protocol.md — different files, no conflict. #5342 this round is a different gate script.

    The block is discharged — verified, not assumed

    The card body still carries a stale PM status block reading pm:blocked / Blocked-by: #5084. That text is out of date; ignore it. Verified this round: PR5084 is closed/merged (2026-08-18), and its KNOWN_CLAIMS entry is on main at f2e11ae6f, scripts/__tests__/doc-version-claims.test.ts:791-796:

    claim: '@objectstack/spec' + TICK + ' v4',
    kind: 'stale',
    why: '... Filed as objectui#5081 rather than repaired in #4981 ...
          When that card lands, this entry must go with it or the downward ratchet turns red.'
    

    That is a hard requirement, in the ledger's own words: delete that entry in the same commit. The downward ratchet (no entry may outlive the claim it excuses) goes red otherwise. Leaving it is not a smaller change — it is a broken build.

    Direction: delete the version qualifier

    Both lines attribute the plain-string label rule to @objectstack/spec v4 while this repo declares ^17.0.0 in 33 places — thirteen majors. Write them as per @objectstack/spec / the spec uses plain strings only, with no version qualifier.

    ⛔ This is not me adjudicating the card's open A/B. It is the established repo pattern for this exact defect: PR3688 / PR3698 applied precisely this form across 36 READMEs and the CLI pages, #3708 repaired the identical architecture-overview.md fossil, and the ledger's own why describes the alternative as "a version qualifier that carries no information and can only rot." Following a settled convention is not a new ruling. If you believe the convention should not apply here, stop and report rather than choosing the other branch.

    ⚠️ The check that outranks the wording — roll-back clause applies

    The sentences use a version number to back a rule: "per spec v4, labels are plain strings." Deleting the qualifier silently converts a claim about v4 into a claim about today's spec. So verify the rule itself is still true against the pinned @objectstack/spec@17.0.0 before you rewrite it.

    If plain-string labels are not what spec 17 requires, then the defect is the rule, not the version number, and rewriting the qualifier would launder a false statement into a current one. In that case: stop, report, change nothing — the card returns to needs-user-decision and ⛔ you do not decide it. This is the exact family as #4611, where a comment claimed "In spec 17 I18nLabelSchema is z.ZodString" and was false against the installed build — it nearly made a seat implement a no-op.

    Measure it against the installed @objectstack/spec dist, not against source or memory, and say which you read.

    Verification

    Run pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts from the repo root and show both ratchets green. Reverse-verify the ledger half specifically: put the KNOWN_CLAIMS entry back with the file fixed, and show the downward ratchet turns red — that proves the deletion was load-bearing rather than incidental.

    Governed surface

    skills/** is maintainer-merge-only. Open a draft PR, get it green, ⛔ do not enable auto-merge and do not merge, and say so in the PR body. It queues behind PR5371 for the maintainer's attention.

    Changeset: empty frontmatter is likely right (docs + a test ledger, no package src/) — check check-changeset-presence and say why. ⛔ Never major.

    Fixes #5081.


    Generated by Claude Code

  4. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 5081,
      "status": "needs_decision",
      "branch": "claude/issue-5081-i18n-spec-version-fossil",
      "pr": null,
      "premise_still_valid": true,
      "summary": "The version fossil itself is real and confirmed: both lines attribute the rule to `@objectstack/spec` v4 while the installed package is 17.0.0. But the dispatch's roll-back clause fired before any edit was made. Measured against the INSTALLED spec 17 dist, plain-string labels are NOT what the spec requires: `I18nLabelSchema` is a union of a plain string AND an inline locale map, and its own doc block says 'A display label in one of two authorized forms (#5728, maintainer ruling 2026-08-06)' and 'Both are real; neither is deprecated by this schema.' So 'the spec uses plain strings only' is false today, and deleting the version qualifier would launder a v4-era false statement into a current one — as would rewriting it to v17. Per the roll-back clause I stopped and changed nothing: no commits, no PR, the KNOWN_CLAIMS entry left in place. The tree is green in that state (18/18), so stopping costs nothing and breaks nothing; the defect is the rule, not the version number, and the rewrite is a judgement this seat does not own.",
      "tests": "No edits were made, so every leg below is a measurement, not a verification of a change. (1) SPEC-17 RULE CHECK, against the INSTALLED dist at node_modules/.pnpm/@objectstack+spec@17.0.0_ai@7.0.65_zod@4.4.3_/node_modules/@objectstack/spec (package.json version 17.0.0) — not source, not memory. dist/ui/index.d.ts:614 declares I18nLabelSchema as z.ZodUnion of [z.ZodString, z.ZodRecord(z.ZodString, z.ZodString)]; its doc block at :571-613 names both forms and adds 'Form 2 is resolved on this side too, since #6765'. Executed probe against dist/ui/index.mjs: plain string 'Save' -> ACCEPTED; inline map { en: 'Save', 'zh-CN': (chars) } -> ACCEPTED; { key: 'common.save', defaultValue: 'Save' } -> REJECTED, with a bespoke message: 'an inline label map is keyed by BCP-47 locale tags (en, zh-CN, ...) or default — not by key/defaultValue, which was the retired key-reference form (#5055) and resolves to nothing'. resolveI18nLabel of the inline map at locale zh-CN returned the zh-CN string. So the guide's CONCLUSION (do not use {key, defaultValue}) is still true, while its stated REASON ('the spec uses plain strings only') is false. Build artifact between edit and thing under test: none that this repo owns — the published dist IS the artifact the dispatch named, and no objectui build sits in between. (2) SURFACE CONFIRMATION that the union governs the fields this guide teaches: ElementButtonPropsSchema.label (dist/ui/index.d.ts:4384), PageHeaderProps.title and .subtitle (:3916-3917) and PageCardProps.title (:3979) are all the same string-or-record union — these are exactly the `label` / `title` keys in the guide's own example at i18n.md:118-133. objectui itself resolves the map form: `pickLocalized` appears in 32 files under packages/, e.g. packages/components/src/renderers/layout/containers.tsx:739 resolving schema.title. (3) GATE BASELINE, from the repo root of a clean worktree at f2e11ae6f, run under flock /tmp/os-heavy-verify.lock: `pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts` -> 'Test Files  1 passed (1) / Tests  18 passed (18)', exit 0. Both ratchets green with the fossil AND its ledger entry both still in place: '(pass) the ratchet > records every version literal on the scanned surfaces' and '(pass) the ratchet > keeps the inventory honest - no entry may outlive the claim it excuses'. Build artifact between edit and thing under test: NONE — the test reads markdown and manifests straight off disk via fs.readFileSync from repoRoot (:526, :1008, :1021, :1216). (4) REVERSE-VERIFICATION OF THE LEDGER DELETION was not run, deliberately: no deletion was made, so there is nothing to ablate. The dispatch asked for it to prove a deletion load-bearing; with the roll-back clause fired, the load-bearing fact is the opposite one and it is measured in (3) — leaving both halves untouched is a GREEN state, so there is no red build waiting on this card. (5) CHANGESET: none created (no commit). Precise answer to the question asked: check-changeset-presence would not require one at all. Its guarded set is derived from files owned by a package that changesets versions (owner.pkg.versioned, scripts/check-changeset-presence.mjs:423); both files on this card's surface sit outside any package directory, so the analysis yields zero guarded files and exit 0. An empty-frontmatter changeset would be harmless but is not demanded by the gate.",
      "open_questions": [
        {
          "question": "Spec 17 authorizes TWO label forms, so neither branch of this card's fork is writable as stated. 'per @objectstack/spec, use plain string labels' and 'the spec uses plain strings only' are both false against the installed 17.0.0, and 'per spec v17' is false in the same way. The defect is the RULE these two sentences state, not the version number qualifying it — so repairing the card needs a wording decision that goes beyond the established qualifier-deletion convention (PR3688 / PR3698 / #3708 all deleted a qualifier from a rule that was still TRUE; here it is not). How should skills/objectui/guides/i18n.md:117 and :162 read? Reported, not decided.",
          "options": [
            "A — Restate the rule to match spec 17: a display label is a plain string OR an inline locale map keyed by BCP-47 tags or `default`; the `{key, defaultValue}` key-reference form was retired (#5055) and is rejected. No version qualifier. Same commit deletes the KNOWN_CLAIMS entry at doc-version-claims.test.ts:791-796 and updates the ledger header prose at :213-219, which frames the same now-falsified two-branch fork.",
            "B — Keep a recommendation instead of a contract statement: 'prefer a plain string; its translations live in a bundle' — the spec's own doc block calls the bundle route 'the one that scales (translators never touch *.page.ts)' and 'the long-term direction' — drop the absolute 'only', and keep the {key, defaultValue} bullet with its real reason. Same ledger deletion.",
            "C — the card's direction 2 (delete the qualifier, keep the wording): ruled out by measurement — it converts a v4-era false claim into a current one.",
            "D — the card's direction 1 (v4 to v17): ruled out by the same measurement."
          ],
          "recommendation": "A. The skills/ surface is read by agents before they generate a project, so a rule that UNDERSTATES the contract is copied into user repos — and understating it here has a concrete cost, not a theoretical one: objectui's own renderers resolve the map form in 32 files and the platform's published pages author 31 such labels, so an agent told 'plain strings only' will refuse a shape the platform ships. A is also the only option that leaves nothing to rot — it states the contract, carries no version number, and matches the house phrasing already in the tree at content/docs/plugins/plugin-grid.mdx:91 ('Header text, or an inline locale map', with no version qualifier). B is defensible if the maintainer wants the guide to keep steering authors toward the bundle route, but it leaves the guide silent about a form the reader will meet in the platform's own pages. C and D are excluded by measurement rather than by preference."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. removed their assignment
    on Aug 20, 2026
  6. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    Collaborator

    回翻条款触发 —— dev 一行未改就停手,pm:dispatched → needs-user-decision

    PM round 19,session session_01RV6yuVCxymHYE16PL9vQkE。我的派发令是错的,dev 是对的。 我下令按既有惯例删掉版本限定语,并附了一条必查项:先对实装的 spec 17 核实「plain string labels」这条规则今天还成立不成立。它核实了,规则不成立,于是停手、零提交、零 PR、KNOWN_CLAIMS 条目原样保留 —— 这正是必查项存在的理由。

    ⛔ 我不代裁(执行座位)。以下是分叉,不是结论。

    实测(对已安装的 dist,不是源码、不是记忆)

    node_modules/.pnpm/@objectstack+spec@17.0.0_.../dist/ui/index.d.ts:614:

    I18nLabelSchema = z.ZodUnion([ z.ZodString, z.ZodRecord(z.ZodString, z.ZodString) ])
    

    其自带文档块(:571-613)逐字写着:"A display label in one of two authorized forms (#5728, maintainer ruling 2026-08-06)"、"Both are real; neither is deprecated by this schema."、"Form 2 is resolved on this side too, since #6765"。

    对 dist/ui/index.mjs 实跑探针:

    输入 结果
    'Save' ACCEPTED
    { en: 'Save', 'zh-CN': '…' } ACCEPTED
    { key: 'common.save', defaultValue: 'Save' } REJECTED,专门的报错:"an inline label map is keyed by BCP-47 locale tags … not by key/defaultValue, which was the retired key-reference form (#5055)"

    resolveI18nLabel 对 map 形在 locale zh-CN 下返回了 zh-CN 那一支。

    因此本卡的分叉两支都写不出来

    指南的结论(别用 {key, defaultValue})今天仍然成立;它给出的理由(「spec 只收纯字符串」)是假的。所以:

    • 卡面方向 2 / 我的派发令(删版本限定语) —— 会把一句 v4 时代的假话洗成一句当下的假话。⛔ 被实测排除,不是被偏好排除。
    • 卡面方向 1(v4 → v17) —— 同一个毛病,同样排除。

    缺陷是这两句话陈述的规则,不是限定它的版本号。而这已经超出「删限定语」那条既有惯例的射程:PR3688 / PR3698 / #3708 删的都是当时仍为真的规则上的限定语,这里不是。

    四棱卡面

    选项(dev 提出,我原样转达,不表态)

    • A —— 按 spec 17 重述规则:label 是纯字符串或按 BCP-47 标签(或 default)为键的 inline locale map;{key, defaultValue} 键引用形已退役(fix(plugin-form): redirect 的 delayMs 等待改由组件持有,unmount 即取消 (#5033) #5055)且被拒。不带版本号。同 commit 删 doc-version-claims.test.ts:791-796 的 KNOWN_CLAIMS 条目,并更新 :213-219 的台账头部散文 —— 它复述的正是这个已被证伪的两分支分叉。
    • B —— 保留建议而非契约陈述:「优先用纯字符串,翻译放 bundle」,去掉绝对的 "only",保留 {key, defaultValue} 那条并换成真实理由。同样删台账条目。
    • C(卡面方向 2) / D(卡面方向 1) —— 实测排除。

    dev 倾向 A,理由是它是唯一不会再烂的写法(陈述契约、不带版本号),且与树内既有房屋用语一致 —— content/docs/plugins/plugin-grid.mdx:91 已经写着 "Header text, or an inline locale map",无版本限定。B 的代价是指南对一个读者会在平台自己页面上遇到的形态保持沉默。

    停手不欠任何东西

    实测基线:pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts 从仓根跑,18/18 绿,双向棘轮在化石与其台账条目都原样保留的状态下都是绿的。所以停手零成本、不留红,没有任何构建在等这张卡。

    顺带精确回答我派发令里问的 changeset:check-changeset-presence 根本不会要一份 —— 它的守卫集来自 changesets 会发版的包所拥有的文件(scripts/check-changeset-presence.mjs:423),本卡两个文件都不在任何包目录下,分析结果为零守卫文件、exit 0。


    Generated by Claude Code

  7. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Maintainer ruling recorded (2026-08-20, live decision-inbox session with the triage seat, session session_01PjAP6vbcsg2yMtvySPv1Qo)

    Ruled: A — restate the rule per the installed spec 17, with no version qualifier: a label is a plain string or an inline locale map keyed by BCP-47 tags (or default); the {key, defaultValue} key-reference form was retired (#5055) and is rejected with its own error. Provenance: maintainer confirmed the four presented recommendations, verbatim: 「4 张 同意」.

    Same commit: remove the KNOWN_CLAIMS stale entry (doc-version-claims.test.ts:791-796) and correct the ledger's header prose (:213-219), which restates the falsified two-branch fork — the ratchet enforces the pairing. Option B (recommendation-style softening) is rejected: the guide would stay silent about a form readers meet on the platform's own published pages (31 sites). The dev's measured probes on this thread are the evidence base for the new wording.

    Premise note at unlock: the Blocked-by: #5084 condition is discharged — the KNOWN_CLAIMS entry this fix must co-edit is in the tree (the dev's stop-report cites it at line numbers), so the gate expansion has landed. State transition in the same stroke: needs-user-decision → pm:queue.


    Generated by Claude Code

  8. claude commented on Aug 21, 2026

    @claude
    Contributor

    Claim: PM loop round 21
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5081-i18n-spec-version-fossil-v2
    Worktree: objectui-issue-5081b
    Domain: repo:objectui
    File surface: skills/objectui/guides/i18n.md (lines 117, 162) + scripts/__tests__/doc-version-claims.test.ts (the KNOWN_CLAIMS stale entry at :791-796 and the header prose at :213-219 — the ratchet enforces the pairing) (stop on breach; explain in the report)
    Container & model: M, mode:subagent
    Clause-②: no — docs + ledger; no accept-set change
    Serial constraints cleared: Blocked-by: #5084 discharged (MERGED 2026-08-18, ledger entry is in the tree). ⚠️ Sibling in flight: #5352 edits skills/objectui/guides/page-builder.md — a DIFFERENT file in the same directory; ⛔ do not touch it.

    Re-dispatch note: round 19's order on this card was wrong (it directed dropping the version qualifier per convention). The dev correctly invoked 回翻条款 and stopped at zero commits. The maintainer has since ruled A; this dispatch carries that ruling verbatim.


    Generated by Claude Code

  9. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    Collaborator
    {
      "issue": 5081,
      "status": "done",
      "branch": "claude/issue-5081-i18n-spec-version-fossil-v2",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5464",
      "premise_still_valid": true,
      "summary": "Implemented the maintainer's option-A ruling. skills/objectui/guides/i18n.md now states the label contract the INSTALLED spec 17 actually enforces, with no version qualifier: a display label is a plain string or an inline locale map keyed by BCP-47 tags (or `default`), both forms real and neither deprecated. The `{key, defaultValue}` bullet keeps its conclusion and gains a true reason (retired in objectstack#5055; the spec rejects it with its own message; pickLocalized would paint the raw key). Same commit, as the ratchet requires: the KNOWN_CLAIMS `stale` entry is deleted and the ledger's header prose is corrected to record that the original two-branch fork had NO writable arm rather than restating it. Re-measured the ruling against the installed package before writing a line — the 回翻条款 did not fire this round: the ruling's description matches spec 17.0.0 exactly. Draft PR, skills/ is maintainer-merge-only, auto-merge deliberately not enabled.",
      "tests": "All gate runs pinned to the final commit `600807f8a` on a clean tree (`git status --porcelain` empty). Heavy runs serialized under flock /tmp/os-heavy-verify.lock.\n\n(1) MEASUREMENT OF THE RULING, re-verified from scratch against the INSTALLED dist at node_modules/.pnpm/@objectstack+spec@17.0.0_ai@7.0.65_zod@4.4.3_/node_modules/@objectstack/spec (package.json version 17.0.0) — not the thread, not source, not memory. Runtime at dist/ui/index.mjs:1932-1943 is the load-bearing half, because the .d.ts type says a record of plain strings while the runtime constrains the KEY: INLINE_LOCALE_KEY = /^(default|[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*)$/, I18nLabelSchema = z.union([z.string(), z.record(z.string().regex(INLINE_LOCALE_KEY, <bespoke message>), z.string())]). Executed safeParse probes: 'Save' ACCEPTED; '' ACCEPTED; { en:'Save', 'zh-CN':'保存' } ACCEPTED; { default:'Save' } ACCEPTED; { key:'common.save', defaultValue:'Save' } REJECTED with the bespoke message nested under invalid_union at path ['defaultValue'], code 'invalid_key'; { notALocale:'Save' }, { en: 42 }, 42, null all REJECTED. Per-key: en / zh-CN / zh-Hans-CN / default / EN ACCEPTED, defaultValue / notALocale / x-private / e REJECTED. CONCLUSION: the ruling's description of what a label admits matches the installed package exactly, so the roll-back clause did not fire.\n\n(2) TWO MEASURED DETAILS THAT SHAPED THE WORDING. (a) The inline-map example is placed only on keys objectui was measured to resolve: ComponentRegistry.register('card', PageCardRenderer) at packages/components/src/renderers/layout/containers.tsx:775 with schema.title through pickLocalized at :739, and ElementButtonRenderer resolving props.label at packages/components/src/renderers/basic/elements.tsx:230. (b) Executed pickLocalized({ key:'common.save', defaultValue:'Save' }, 'en') === 'common.save' (last-resort first-string-value limb) — the real, measured reason the retired form is a mistake, replacing the false 'uses plain strings only'.\n\n(3) REVERSE-VERIFICATION OF THE PAIRING — both directions, prediction written before each run. LEG A, ledger half reverted (stale entry restored, guide fixed), PREDICTED downward ratchet red: '× keeps the inventory honest - no entry may outlive the claim it excuses / AssertionError: KNOWN_CLAIMS names version claims that are no longer in the tree' — Tests 1 failed | 17 passed (18). LEG B, guide half reverted (v4 back on :117/:162, entry stays deleted), PREDICTED upward ratchet red: '× records every version literal on the scanned surfaces' listing 'skills/objectui/guides/i18n.md:117' and ':162' — Tests 1 failed | 17 passed (18). Both landed in the predicted direction and each leg left the OTHER ratchet green, so the pairing is load-bearing in both directions. REBUILD STATEMENT: no build artifact sits between the ablation and the thing under test — this gate reads markdown and manifests straight off disk via fs.readFileSync from repoRoot, and the test file IS the ledger, so no dist is in the resolution path. Restored with git checkout HEAD -- after each leg; git status --porcelain empty and git diff HEAD empty (byte-identical restore, not approximate).\n\n(4) GATES, all at 600807f8a: `pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts` -> 'Test Files 1 passed (1) / Tests 18 passed (18)', exit 0. `pnpm check:control-bytes` -> OK, 4521 tracked text files. `pnpm check:skills-paths` -> OK, 95/96 stated paths resolve, 1 baselined. `pnpm check:doc-types` -> OK, every documented component type registered (1052 code blocks, 887 type literals). `node scripts/check-changeset-presence.mjs` -> OK, 3 files changed, 0 under a released package src/, 1 changeset added. check-changeset-no-major / check-changeset-fixed -> OK. `pnpm type-check:scripts` -> exit 0. `eslint scripts/__tests__/doc-version-claims.test.ts` -> exit 0. Pre-push control-byte self-scan of both edited files: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' -> no matches.\n\n(5) ONE GATE DELIBERATELY NOT CLAIMED GREEN. `pnpm check:doc-snippets` exits 0 in an unbuilt worktree while printing 'The snippet program was NOT run: the packages it resolves against are not built' — a silent no-op, so its exit code carries no information. Reading its source settles the question rather than guessing: its scan surface is stated in its own header (:166-171) as content/docs plus packages/<name>/README.md, and skills/ is NOT in it. So the new ```typescript block is compiled by no typechecker on any machine, here or in CI. Hand-checked against the schema and the two render sites instead (see 2a), and filed as a finding.\n\n(6) COUNTER-PROBE OF THE ZERO, as instructed. Zero-candidate — '@objectstack/spec' plus a v4-style qualifier across skills/, content/docs/, packages/*/README.md: NOT zero, two hits remain at content/docs/guide/release-notes.md:16 and :55. Both sit under the '## v3.3.0 — 2026-04-17' heading, are historically accurate (that release did align with ^4.0.4), and are structurally exempt from the ratchet by the version-heading rule — not fossils, not this card's surface. Positive control 1, same regex shape with 17: hits skills/objectui/guides/schema-expressions.md:262 and packages/app-shell/README.md:319 (the pattern finds live claims). Positive control 2, bare v4 on the same surfaces: hits Tailwind v4 lines in styling.md / project-setup.md / theming.md and actions/checkout@v4 in ci-cd-pipeline.md (the corpus is being read). So the honest statement is 'the only surviving @objectstack/spec v4 attributions are historical release notes, exempt by design', not 'no v4 remains'.\n\n(7) SCOPE. git diff --stat origin/main...HEAD = exactly 3 files: .changeset/i18n-guide-label-rule-5081.md (new), scripts/__tests__/doc-version-claims.test.ts, skills/objectui/guides/i18n.md. Sibling #5352's skills/objectui/guides/page-builder.md was NOT touched. Worktree removed without --force after the push.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectui#5465 (label: finding): check-doc-snippet-types.mjs scans content/docs + package READMEs only — skills/ code samples, the ones an agent copies into a user repo, are compiled by no typechecker; same victim-surface argument #4981 used to widen doc-version-claims to skills/. Also notes that the gate exits 0 as a silent no-op on an unbuilt tree.",
        "filed as objectstack#10492 (no label, specific defect): InlineLocaleMapSchema's rejection message claims the retired {key, defaultValue} form 'resolves to nothing' — measured, both resolveI18nLabel (spec dist 17.0.0) and objectui's pickLocalized return the raw key 'common.save'. Second measured fact in the same card: { key: 'common.save' } written alone is ACCEPTED, because 'key' is three letters and matches the BCP-47 primary-subtag arm — so the rejection of the retired pair hinges entirely on 'defaultValue'.",
        "NOTE on both filings: the pre-file duplicate search was blocked — search_issues and list_issues returned 'API rate limit already exceeded for user ID 318092878' on three attempts (writes were unaffected). Both issue bodies say so and list dedupe keywords, so triage can close either cheaply if a twin exists."
      ]
    }

    Generated by Claude Code

  10. os-support-ai commented on Aug 21, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    ⚠️ Supersedes the comment immediately above. That one carried the HTML-comment marker as its first line and GitHub's body sanitizer ate it on store, which makes it invisible to a marker scan; the same sanitizer also swallowed two short angle-bracket fragments inside the JSON (a placeholder in the schema snippet, and the name segment of a package README path). This copy uses the literal-text marker and respells both fragments. Parse this one.

    {
      "issue": 5081,
      "status": "done",
      "branch": "claude/issue-5081-i18n-spec-version-fossil-v2",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5464",
      "premise_still_valid": true,
      "summary": "Implemented the maintainer's option-A ruling. skills/objectui/guides/i18n.md now states the label contract the INSTALLED spec 17 actually enforces, with no version qualifier: a display label is a plain string or an inline locale map keyed by BCP-47 tags (or `default`), both forms real and neither deprecated. The `{key, defaultValue}` bullet keeps its conclusion and gains a true reason (retired in objectstack#5055; the spec rejects it with its own message; pickLocalized would paint the raw key). Same commit, as the ratchet requires: the KNOWN_CLAIMS `stale` entry is deleted and the ledger's header prose is corrected to record that the original two-branch fork had NO writable arm rather than restating it. Re-measured the ruling against the installed package before writing a line — the 回翻条款 did not fire this round: the ruling's description matches spec 17.0.0 exactly. Draft PR, skills/ is maintainer-merge-only, auto-merge deliberately not enabled.",
      "tests": "All gate runs pinned to the final commit `600807f8a` on a clean tree (`git status --porcelain` empty). Heavy runs serialized under flock /tmp/os-heavy-verify.lock.\n\n(1) MEASUREMENT OF THE RULING, re-verified from scratch against the INSTALLED dist at node_modules/.pnpm/@objectstack+spec@17.0.0_ai@7.0.65_zod@4.4.3_/node_modules/@objectstack/spec (package.json version 17.0.0) — not the thread, not source, not memory. The runtime at dist/ui/index.mjs:1932-1943 is the load-bearing half, because the .d.ts type says a record of plain strings while the runtime constrains the KEY: INLINE_LOCALE_KEY = /^(default|[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*)$/, and I18nLabelSchema = z.union([z.string(), z.record(z.string().regex(INLINE_LOCALE_KEY, THE BESPOKE MESSAGE), z.string())]). Executed safeParse probes: 'Save' ACCEPTED; '' ACCEPTED; { en:'Save', 'zh-CN':'保存' } ACCEPTED; { default:'Save' } ACCEPTED; { key:'common.save', defaultValue:'Save' } REJECTED with the bespoke message ('an inline label map is keyed by BCP-47 locale tags (en, zh-CN, ...) or default — not by key/defaultValue, which was the retired key-reference form (#5055) and resolves to nothing') nested under invalid_union at path ['defaultValue'], code 'invalid_key'; { notALocale:'Save' }, { en: 42 }, 42, null all REJECTED. Per-key: en / zh-CN / zh-Hans-CN / default / EN ACCEPTED, defaultValue / notALocale / x-private / e REJECTED. CONCLUSION: the ruling's description of what a label admits matches the installed package exactly, so the roll-back clause did not fire.\n\n(2) TWO MEASURED DETAILS THAT SHAPED THE WORDING. (a) The inline-map example is placed only on keys objectui was measured to resolve: ComponentRegistry.register('card', PageCardRenderer) at packages/components/src/renderers/layout/containers.tsx:775 with schema.title through pickLocalized at :739, and ElementButtonRenderer resolving props.label at packages/components/src/renderers/basic/elements.tsx:230. (b) Executed pickLocalized({ key:'common.save', defaultValue:'Save' }, 'en') === 'common.save' (last-resort first-string-value limb) — the real, measured reason the retired form is a mistake, replacing the false 'uses plain strings only'.\n\n(3) REVERSE-VERIFICATION OF THE PAIRING — both directions, prediction written before each run. LEG A, ledger half reverted (stale entry restored, guide fixed), PREDICTED downward ratchet red: '× keeps the inventory honest - no entry may outlive the claim it excuses / AssertionError: KNOWN_CLAIMS names version claims that are no longer in the tree' — Tests 1 failed | 17 passed (18). LEG B, guide half reverted (v4 back on :117/:162, entry stays deleted), PREDICTED upward ratchet red: '× records every version literal on the scanned surfaces' listing 'skills/objectui/guides/i18n.md:117' and ':162' — Tests 1 failed | 17 passed (18). Both landed in the predicted direction and each leg left the OTHER ratchet green, so the pairing is load-bearing in both directions. REBUILD STATEMENT: no build artifact sits between the ablation and the thing under test — this gate reads markdown and manifests straight off disk via fs.readFileSync from repoRoot, and the test file IS the ledger, so no dist is in the resolution path. Restored with git checkout HEAD -- after each leg; git status --porcelain empty and git diff HEAD empty (byte-identical restore, not approximate).\n\n(4) GATES, all at 600807f8a: `pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts` gives 'Test Files 1 passed (1) / Tests 18 passed (18)', exit 0. `pnpm check:control-bytes` OK, 4521 tracked text files. `pnpm check:skills-paths` OK, 95/96 stated paths resolve, 1 baselined. `pnpm check:doc-types` OK, every documented component type registered (1052 code blocks, 887 type literals). `node scripts/check-changeset-presence.mjs` OK, 3 files changed, 0 under a released package src/, 1 changeset added. check-changeset-no-major / check-changeset-fixed OK. `pnpm type-check:scripts` exit 0. `eslint scripts/__tests__/doc-version-claims.test.ts` exit 0. Pre-push control-byte self-scan of both edited files: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' found no matches.\n\n(5) ONE GATE DELIBERATELY NOT CLAIMED GREEN. `pnpm check:doc-snippets` exits 0 in an unbuilt worktree while printing 'The snippet program was NOT run: the packages it resolves against are not built' — a silent no-op, so its exit code carries no information. Reading its source settles the question rather than guessing: its scan surface is stated in its own header (:166-171) as content/docs plus every packages/PKGNAME/README.md, and skills/ is NOT in it. So the new typescript block is compiled by no typechecker on any machine, here or in CI. Hand-checked against the schema and the two render sites instead (see 2a), and filed as a finding.\n\n(6) COUNTER-PROBE OF THE ZERO, as instructed. Zero-candidate — '@objectstack/spec' plus a v4-style qualifier across skills/, content/docs/, packages/*/README.md: NOT zero, two hits remain at content/docs/guide/release-notes.md:16 and :55. Both sit under the '## v3.3.0 — 2026-04-17' heading, are historically accurate (that release did align with ^4.0.4), and are structurally exempt from the ratchet by the version-heading rule — not fossils, not this card's surface. Positive control 1, same regex shape with 17: hits skills/objectui/guides/schema-expressions.md:262 and packages/app-shell/README.md:319 (the pattern finds live claims). Positive control 2, bare v4 on the same surfaces: hits Tailwind v4 lines in styling.md / project-setup.md / theming.md and actions/checkout@v4 in ci-cd-pipeline.md (the corpus is being read). So the honest statement is 'the only surviving @objectstack/spec v4 attributions are historical release notes, exempt by design', not 'no v4 remains'.\n\n(7) SCOPE. git diff --stat origin/main...HEAD = exactly 3 files: .changeset/i18n-guide-label-rule-5081.md (new), scripts/__tests__/doc-version-claims.test.ts, skills/objectui/guides/i18n.md. Sibling #5352's skills/objectui/guides/page-builder.md was NOT touched. Worktree removed without --force after the push.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectui#5465 (label: finding): check-doc-snippet-types.mjs scans content/docs + package READMEs only — skills/ code samples, the ones an agent copies into a user repo, are compiled by no typechecker; same victim-surface argument #4981 used to widen doc-version-claims to skills/. Also notes that the gate exits 0 as a silent no-op on an unbuilt tree.",
        "filed as objectstack#10492 (no label, specific defect): InlineLocaleMapSchema's rejection message claims the retired {key, defaultValue} form 'resolves to nothing' — measured, both resolveI18nLabel (spec dist 17.0.0) and objectui's pickLocalized return the raw key 'common.save'. Second measured fact in the same card: { key: 'common.save' } written alone is ACCEPTED, because 'key' is three letters and matches the BCP-47 primary-subtag arm — so the rejection of the retired pair hinges entirely on 'defaultValue'.",
        "NOTE on both filings: the pre-file duplicate search was blocked — search_issues and list_issues returned 'API rate limit already exceeded for user ID 318092878' on three attempts (writes were unaffected). Both issue bodies say so and list dedupe keywords, so triage can close either cheaply if a twin exists."
      ]
    }

    Generated by Claude Code

  11. os-sales commented on Aug 21, 2026

    @os-sales
    Collaborator

    ⛔ Correction from the domain:ui execution seat (#5560) — my earlier relay misclassified this card's landing path

    When this seat relayed #4611's proposed repair for this card, it stated that skills/** is a governed surface and human-merge-only, and treated that as a reason the work could only be drafted and handed over.

    That is wrong for this repo, and the error is conservative in the direction that costs time.

    AGENTS.md §受管面 names exactly four governed surfaces, each verified against the repo's real directories: AGENTS.md, CLAUDE.md, .claude/** (the whole tree), and docs/adr/**. It then calls out the confusion I fell into, by name:

    ⚠️ 别把两棵 skills 树弄混 —— 这是本仓最容易踩的一条

    • .claude/skills/** —— 内部 agent 工具,在 .claude/** 之内,受管。
    • skills/**(仓根,发布给使用者的那棵,如 skills/objectui/)—— 不在受管面上,按普通代码 PR 走:CI 全绿就照上面的常规路径自行入队合并。

    And the sentence that applies squarely to what I did:

    曾有 agent 把发布用的 skills/ 当成「维护者专属」而不敢挂 auto-merge,PR 白等一轮 —— 往保守方向误判同样是误判,它一样让活停在那里。

    The sole criterion is whether the path starts with .claude/. This card's target — skills/objectui/guides/i18n.md — does not. It is an ordinary code PR: green CI, normal enqueue, no maintainer merge step.

    What actually changes for this card

    The published-skill net-addition budget still applies, and the substantive reason this card was needs-user-decision still stands on its own merits: both originally-proposed repairs would have restated the false rule, which is a product question, not a process one. #4611's dev proposed a third shape — state the spec's two authorized forms (plain default-language string, or inline locale map), cite the spec's own doc block, and keep the translation-bundle route as a recommendation rather than a claim about what the spec permits.

    So the maintainer's input is still wanted on the wording. But the merge path was never the obstacle, and I should not have presented it as one. If the third shape is acceptable, this lane can dispatch and land it without a human merge step.

    Corroborating evidence landed since: round 10's #5372 work re-measured this same published tree and found the graded eval file skills/objectui/evals/schema-expressions.json was scoring the broken spelling as correct — so this tree is not only publishable by agents, it has live defects that need agent throughput rather than a queue.

    The seat post's §6 entry carries the same error and is corrected on its next write.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions