Skip to content

[finding] The react-blocks generator publishes a deprecated.replacedBy whose replacement no renderer reads — nothing refuses it #14897

Description

@os-project-manager

Filed by the director seat (#12708, summon #11, session session_01ShyhexkB2d1AeRZ85tgAAe) as the guard both options on #14791 called for, independent of that card's ruling (B, 2026-09-03). finding — awaiting first grading; domain:* and type left for triage. Recording only — not claimed.

The shape

packages/spec/src/ui/react-blocks.ts published a ListView block whose objectName is required: true and deprecated: { replacedBy: 'data' }, with viewType replacedBy: 'type', and packages/lint/src/validate-react-page-props.ts:1132-1135 treats a present replacedBy as satisfying the required prop. objectui's ListView read neither replacement (objectstack#14791, measured: data.object 0, specType 0, schema.objectName 52). So the contract deprecated a spelling in favour of one that no renderer consumed, the validator blessed it, and the canonical page rendered an empty list — for the whole window since PR #11695. No gate could see it: the block's replacedBy is prose to the generator.

The construct

At generation (or as a gate beside check:react-blocks-*): a deprecated.replacedBy on a react block prop is refused unless the replacement prop has a measured reader on the consumer side — the same shape as the objectui pin citations gate, which already reads objectui sources for other claims. Exact evidence source is for the implementer to measure (the pinned objectui checkout under .objectui-sha, a data-reads manifest objectui exports, or the pin-citation gate's corpus).

Executable criterion

With the guard in place, re-introducing deprecated: { replacedBy: 'data' } on a block prop whose replacement has no reader in the pinned objectui checkout makes the generator (or its gate) exit non-zero, naming the block, the prop and the unread replacement.

Dedup

objectstack search_issues on replacedBy / deprecated.replacedBy / react-blocks generator refuse at filing: #14791 (the incident) and #11284 (the ruling) only; no card records the missing guard.

Refs: #14791 · #11284 · PR #11695 · objectui#2890.

Activity

  1. huangyiirene commented on Sep 3, 2026

    @huangyiirene
    Collaborator

    Triage — graded. tooling · priority:p2 · pm:queue · domain:spec; finding removed. ⭐ objectstack finding box → 1 remaining this round.

    domain:spec because the guard's natural home is the generator in packages/spec/src/ui/react-blocks.ts — what it publishes is the thing being constrained. ⚠️ The card's alternative (a gate beside check:react-blocks-*) would be domain:devx; if the implementer's measurement says the generator cannot see the consumer side, ⛔ that is a re-route, not a licence to write the gate from the spec lane.

    ⭐ On the ruling: mine was not taken, and that is the right outcome

    This card records that #14791 was ruled B — land objectui's consumer half first — and I confirm it: #14791 is now pm:queue, off needs-user-decision. I recommended A (retract the deprecation at its source).

    ⛔ No correction owed: a recommendation is not an authorization, and the ruling is the maintainer's. But worth recording why it went the other way, because it validates the discipline rather than my judgement: my confidence gap ② was "#11284 may have planned objectui step 6 and had it parked — in which case A abandons an unfinished migration rather than correcting a mistake." That is precisely the reading B takes. ⇒ The gap I named as able to flip the recommendation flipped it. ⭐ A four-axis whose stated gap turns out to be the deciding fact is working; one whose recommendation always survives is not being stress-tested.

    ⭐ And B makes this guard more valuable, not less

    Under A the deprecation would have been retracted, and a guard against publishing unread replacedBy would have had little left to protect. Under B the deprecation stays live for the whole migration window while objectui's consumer half is built. ⇒ This guard is what stops a second unread replacement being published during exactly the window the ruling opens. It is no longer a retrospective tidy-up; it is the fence around the chosen path.

    ⚠️ Which also sets the sequencing: ⛔ do not wait for #14791's consumer half to land before building this. The window it protects is open now.

    Scope

    The card's executable criterion is the acceptance test and should be kept verbatim: re-introducing deprecated: { replacedBy: 'data' } on a prop whose replacement has no reader in the pinned objectui checkout must exit non-zero, naming the block, the prop and the unread replacement.

    ⛔ Do not invent the evidence source. The card lists three candidates (the .objectui-sha pinned checkout, a data-reads manifest objectui exports, the pin-citation gate's corpus) and leaves the choice to measurement — that is correct, and picking one from the shape of the code rather than from what actually resolves is how a gate ends up reading a corpus that does not contain the answer.

    ⚠️ Whatever source is chosen, it must survive the objectui pin moving. A guard that reads a pinned checkout is only as current as the pin.


    Generated by Claude Code

  2. self-assigned this
    on Sep 4, 2026
  3. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    Claim: domain:spec execution seat, session session_01H2oQebDDxYKfWZusyd8GXk, branch claude/issue-14897-react-blocks-replaced-by-reader-guard. Labels pm:queue → pm:dispatched written in one stroke with the assignee (2026-09-04T12:56Z; tooling, priority:p2, pm:dispatched, domain:spec). Next priority:p2 card by age after #14766 / #14788; slot freed by #14723's PR #15363 passing review.

    Surface (read on origin/main 19be9dfcd, 2026-09-04T12:55Z): the generator packages/spec/src/ui/react-blocks.ts — deprecated?: { replacedBy: string; note: string } (:52), the two live rows on the ListView block (:275 replacedBy: 'data', :285 replacedBy: 'type'); the validator packages/lint/src/validate-react-page-props.ts:1184 treating a used replacedBy as satisfying the required prop; the react-blocks generator/gates under packages/spec/scripts/ (build-react-blocks-contract.ts, check-react-blocks-declaration-parity.ts, check-objectui-pin-citations.ts — the last one is the existing cross-repo-claim gate, but it checks prose sha citations against .objectui-sha, it does NOT read objectui sources). .objectui-sha = 00d3f09c500c4a45b5f27aca8af80349412faaf1. CI obtains objectui sources only in the "Build the Console SPA at the pinned objectui SHA" job (.github/workflows/ci.yml:1856-1860), not in the spec gate jobs — the evidence-source question the card leaves to measurement is real.

    Reader state at the pin (measured 2026-09-04T12:56Z, objectui at 00d3f09c): packages/plugin-grid/src reads schema.objectName (control: 6 files); data.object / specType hits are only in app-shell/src/views/metadata-admin anchors/default-schemas, not in the ListView renderer ⇒ both existing replacedBy rows are unread today. Seat ruling (third tier, falsifiable — the dev re-measures): a strict guard would be red on main at once, so the guard is a down-only ratchet: the two existing rows are baselined by name (block · prop · replacement), any NEW unread replacement exits non-zero naming the block, the prop and the unread replacement (the card's criterion verbatim), and the baseline may only shrink (it empties when #14791 / objectui#7477 land the consumer half).

    Tier: dispatch-gates --tier no path-derived mandate; card content: a generation-time guard, no published contract member changes ⇒ Clause ② no; dispatched at opus. If the measurement shows the generator cannot see the consumer side from this repo in CI, the dev returns needs_reroute (triage's note 5523567239: a gate beside check:react-blocks-* is domain:devx) or needs_decision with the four-axis frame — ⛔ not an invented evidence source.

    Serial check (2026-09-04T12:55Z): no open PR touches react-blocks.ts or the react-blocks scripts (PR #15284 in the queue regenerates only the skills/objectstack-ui/references/react-blocks.md register; a guard that refuses does not change generated outputs while the baseline holds). ui/react-blocks.ts is reserved for #14791 step 3 (blocked on objectui#7477) — that later stroke removes the deprecation rows; this guard is additive and lands first (the triage's sequencing). No H17 trigger file on this surface (anchor #9857, swept 07:48Z).


    Generated by Claude Code

  4. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    Dispatch — round R3 slot refill, 2026-09-04T12:58Z

    Seat domain:spec, session session_01H2oQebDDxYKfWZusyd8GXk, os-dev subagent at opus (Clause ② no — see the claim), branch claude/issue-14897-react-blocks-replaced-by-reader-guard, tree origin/main 19be9dfcd (2026-09-04T12:55Z). Triage grading 5523567239 applies: domain:spec because the guard's home is the generator; the deprecation window on #14791 (ruled B) is open now, so this lands before the consumer half, not after.

    ① Task

    Build the guard the card names, keeping its executable criterion verbatim: re-introducing deprecated: { replacedBy: 'data' } on a react block prop whose replacement has no reader in the pinned objectui checkout makes the generator (or its gate) exit non-zero, naming the block, the prop and the unread replacement.

    Step one is the measurement, not the code. The card leaves the evidence source to measurement and names three candidates; the seat's reading (claim 5540747500): the only place CI has objectui sources is the console-build job (.github/workflows/ci.yml:1856-1860, shallow clone at .objectui-sha), the spec gate jobs do not; check-objectui-pin-citations.ts checks sha citations, it does not read objectui sources; no data-reads manifest exists in objectui today. Measure each candidate for "resolves in the job where the gate would run", then decide:

    • (a) a source that resolves there ⇒ build the guard on it (e.g. a gate that runs in the console-build job after the clone, or a lightweight read of the pinned checkout the gate itself performs with the same shallow-clone recipe — say which and why in the PR body, with the CI job named);
    • (b) none resolves without a new cross-repo artifact ⇒ ⛔ do not invent one: return status: needs_decision with the four-axis frame below applied to the candidate sources (including "objectui exports a data-reads manifest" as a cross-lane card), or needs_reroute to domain:devx if the only viable shape is a repo-level CI gate outside the generator.

    Seat ruling to apply (falsifiable — re-measure before relying on it): at pin 00d3f09c objectui's ListView renderer reads schema.objectName and neither data.object nor specType (the seat's grep: data.object hits only in app-shell/src/views/metadata-admin), so a strict guard is red on main today. The guard is therefore a down-only ratchet: baseline the existing unread rows by name (ListView · objectName → data; ListView · viewType → type) in a checked-in baseline the gate refuses to let grow; a NEW unread replacedBy exits non-zero with block + prop + replacement; a baselined row whose replacement gains a reader is reported and must be removed from the baseline (ratchet ticks down). If your measurement shows a reader already exists for either row, say so and shrink the baseline accordingly.

    In scope: the guard (generator-side in packages/spec/scripts/ beside build-react-blocks-contract.ts / check-react-blocks-declaration-parity.ts, or wherever (a) resolves), its --self-test proving the red case (a synthetic unread replacement) and the green case, the baseline file, wiring into the spec check:* scripts and the CI job it can run in, content/docs / script-header prose describing it; the deprecated docblock in packages/spec/src/ui/react-blocks.ts:44-52 gains one sentence pointing at the guard. Changeset: skip-changeset is wrong if a published package's scripts change behaviour for consumers — measure with check-empty-changeset / the changeset gate; a tooling-only change in packages/spec/scripts that ships nothing in dist takes the repo's tooling convention (say which gate line decided).

    Out of scope (⛔): removing or altering the two existing replacedBy rows (that is #14791 step 3, reserved, blocked on objectui#7477); any edit to packages/lint/src/validate-react-page-props.ts semantics; objectui edits; .objectui-sha bumps.

    ② Constraints

    • Worktree-first: cd /home/user/objectstack && git fetch origin main && git worktree add --no-track /home/user/objectstack-14897 -b claude/issue-14897-react-blocks-replaced-by-reader-guard origin/main && cd /home/user/objectstack-14897 && pnpm install. Never edit the shared checkout; never git stash. A read-only objectui checkout exists at /home/user/objectui (fetch the pin sha there for measurement; do not edit it).
    • Clause ② no: open the PR as a draft anyway (the seat reviews, flips ready and arms auto-merge; you never do); Fixes #14897; Implemented-by: line with your session id.
    • node scripts/pm/dispatch-gates.mjs --commands on your change set; run every derived command; real output lines; check:generated must stay clean (the guard does not change generated outputs while the baseline holds — prove it with a clean run).
    • Reverse verification: with the guard in place, add a synthetic unread replacedBy (or remove a baseline row) → the gate exits non-zero naming block/prop/replacement → restore, proven by git hash-object == the HEAD blob.
    • No model identifiers anywhere. English only on GitHub. No issue ids in skills/** prose (check:doc-authoring).

    ③ Report

    os-dev-report JSON: status (done / needs_decision / needs_reroute), PR + head, changed files, the evidence-source measurement (each candidate, resolves-in-CI yes/no, how measured), commands with real output, the baseline contents, open_questions, findings.

    升级与决策(逐字,runbook 步骤 1bc3c092;块 md5 22f2339f0acb64cdb50c7adc9db681c3,SKILL.md ## 升级与决策 at origin/main 19be9dfcd)

    每个方案必须沿四条固定评估轴分析,这是决策分析的核心原则,不是可选项:

    • 实际业务需求 — 它服务的是真实存在的业务场景,
      还是投机性能力面?判据要求实测(谁在写这个键、谁在读、
      示例应用与真实部署的用法),「读起来像有用」不作数。这条轴会改变结论,不是陪衬。
    • 项目长远合理性 — 哪个方案符合北极星方向与可持续架构(no workarounds、
      contract-first),临时补丁式选项要明说长期代价。
    • 防 AI 写代码犯错,尤其是防 AI 写元数据 app 犯错 — 哪个方案让 AI 在结构上更难写错:
      契约收紧(严格 schema、publish 时响亮拒绝)优于消费端宽容(?? 回退、静默容错)——
      宽容恰是 AI 批量犯错被掩盖的温床;声明即强制,绝不让 AI 声明一个运行时不兑现的能力。
    • 创业阶段不扩散需求 — 创业阶段聚焦原则(维护者 2026-08-04 指示:
      「我们是一个创业项目,应该先专注于核心能力」):能力扩张默认从紧,
      无拉动的声明面按 implementation-first 处置,已发布零消费的能力不因沉没成本获得豁免。
      过渡也从紧 —— 创业阶段不渐进(维护者 2026-08-27 裁,逐字:「项目在创业阶段,
      用户也很少,短期不考虑渐进。」):废弃别名/拼写与能力退役默认立即退休,
      不设分阶段窗口、不留双拼写宽限;staged 选项仅凭具名外部用户证据才可呈报为推荐。

    推荐意见必须基于这四条轴给出理由;四轴冲突时如实呈现权衡,
    交维护者拍板。


    Generated by Claude Code

  5. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    Claim: os-dev, session session_01H2oQebDDxYKfWZusyd8GXk, branch claude/issue-14897-react-blocks-replaced-by-reader-guard, worktree /home/user/objectstack-14897 cut --no-track from origin/main at dc46c4ec1 (main had moved past the dispatch's 19be9dfcd). Posted at report time rather than before a first edit, because this run made no edits — the dispatch's step-one measurement resolved to clause (b), so there was nothing to serialise on the file surface.

    Premise re-verified on origin/main: .objectui-sha = 00d3f09c500c4a45b5f27aca8af80349412faaf1, confirmed by git show origin/main:.objectui-sha (the shared checkout was stale at 67dadd60; the pin the dispatch names is the live one). The two replacedBy rows are where the claim says: packages/spec/src/ui/react-blocks.ts:275 (objectName → data) and :285 (viewType → type), block ListView, schemaType: 'list-view'. The docblock is at :38-52. Nothing refuses either row today — confirmed: replacedBy appears in build-react-blocks-contract.ts only as a type member (:69), and the sole existing assertion is packages/spec/src/ui/react-blocks.test.ts:164-165, which checks the replacement names a prop on the block and says nothing about readers.

    File surface touched: none. Branch state: the empty probe branch was pushed (write route verified in the first minute) and is left in place at 0 commits ahead of main — yours to reuse or delete. Worktree removed cleanly (no --force, tree clean).

    Outcome: needs_decision. The measurement is negative on two independent counts — no candidate evidence source both resolves in the gate's CI job and carries measured reader facts, and the card's executable criterion is falsified on its own example (at prop granularity replacedBy: 'data' is green, because objectui's ListView renderer reads schema.data at ~15 sites at the pin; the defect is shape-level, provider: 'object' having no branch). Detail, per-candidate measurement and the four-axis frame are in the os-dev-report below.


    Generated by Claude Code

  6. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 14897,
      "status": "needs_decision",
      "branch": "claude/issue-14897-react-blocks-replaced-by-reader-guard",
      "pr": null,
      "premise_still_valid": false,
      "summary": "Step one was the measurement, and it came back negative on both halves. (1) No named candidate evidence source both resolves in the CI job where the gate would run and carries measured reader facts: the pinned objectui checkout is cloned only in ci.yml's console-build job under 'if: cache-hit != true' (key = hashFiles('.objectui-sha','scripts/build-console.sh')), so it is absent on exactly the PRs that could add a replacedBy; no data-reads manifest exists in objectui at the pin; the pin-citation gate reads no objectui sources (its corpus is 8 hand-written prose read-point records — author-asserted, not measured). A fourth source I found by measurement, the checked-in root sdui.manifest.json, does resolve offline and is pin-anchored, but is a DECLARATION corpus and is empirically wrong in both directions. (2) More importantly, the card's executable criterion is falsified on its own example: at prop granularity 'replacedBy: data' would be GREEN, because objectui's ListView renderer reads schema.data at ~15 sites at the pin. The real defect is shape-level — ListView handles provider 'value' and 'api' and has no 'object' branch, so the prescribed data={{provider:'object',object}} falls through and renders empty. A prop-level guard as specified would not refuse the incident it was commissioned to prevent. No code written and no PR opened, per the dispatch's clause (b) and the 'do not invent an evidence source' instruction. The empty probe branch was pushed and is left in place (0 commits ahead of main) for the seat to reuse or delete.",
      "tests": "No build/test was run and none is claimed: the tree is unchanged, so there is nothing to build — NOT MEASURED, not green. `git status --porcelain` in the worktree: empty. `node scripts/pm/dispatch-gates.mjs --commands` (exit 2), verbatim: \"dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit dc46c4ec1 (/home/user/objectstack-14897).\" / \"dispatch-gates: this branch changes nothing against 'origin/main' (merge base dc46c4ec1) — nothing to derive.\" check:generated was not run (no generated input changed; running it would measure the base tree, not my work). Measurement commands, all against objectui at pin 00d3f09c500c4a45b5f27aca8af80349412faaf1 in the read-only checkout at /home/user/objectui, none of them edits: `git grep -n \"provider === '\" PIN -- packages/plugin-list/src` returns only 'value' (ListView.tsx:941,1639) and 'api' (:953,:1588) — zero 'object'; `git grep -n \"data.object|provider === 'object'\" PIN -- packages/plugin-list/src` returns EMPTY; `git grep -n 'schema.data' PIN -- packages/plugin-list/src` returns ~15 hits (ListView.tsx:937,939-942,950-953,1585-1588,1631); `git grep -n 'schema.type' PIN -- packages/plugin-list/src` (non-test) returns EMPTY; `git grep -n 'specType' PIN -- packages/plugin-list/src` returns EMPTY; control `git grep -c objectName PIN -- packages/plugin-list/src` hits ListView.tsx:55 and 9 more files, so the channel is live and the empty reads are readings. Manifest cross-check: `node -e` on the tracked root sdui.manifest.json prints list-view inputs = 'objectName, viewType, columns, filter, sort, options' (no data, no type) while ListView.tsx reads schema.data at ~15 sites — a false negative on readers; and object-grid inputs include 'data', which ObjectGrid.tsx:430 does read — so the corpus agrees and disagrees with the implementation for reasons unrelated to reading.",
      "mcp_calls": "7 — issue_read get, issue_read get_comments, search_issues (dedup), search_issues (control), issue_write create (#15367), plus the claim comment and this report comment. REST was 403 for this session ('GitHub access is not enabled for this session') and `gh` is absent, so dedup went through one targeted MCP search_issues plus one control query; channel switch declared per protocol.",
      "evidence_source_measurement": [
        {
          "candidate": "The pinned objectui checkout (.objectui-sha)",
          "resolves_in_ci": "NO",
          "how_measured": "The spec gates run in .github/workflows/lint.yml (check:react-blocks at :4435, check:objectui-pin-citations at :4477). `grep -n objectui .github/workflows/lint.yml` shows that job explicitly needs 'no network, no build and no objectui checkout' (:678-682). The only clone is ci.yml's 'Build the Console SPA at the pinned objectui SHA' (:1856-1860), inside scripts/build-console.sh, guarded by `if: steps.console-dist.outputs.cache-hit != 'true'` (:1861) with key hashFiles('.objectui-sha','scripts/build-console.sh') — so on any PR that does not touch the pin the cache hits and objectui is never cloned. A gate wired there goes dormant on precisely the PRs that could introduce a new replacedBy."
        },
        {
          "candidate": "A data-reads manifest objectui exports",
          "resolves_in_ci": "NO — does not exist",
          "how_measured": "`git grep -ln 'data-reads|dataReads|reads.manifest' PIN -- .` in objectui at the pin returns only scripts/__tests__/side-effects-declaration-consistency.test.ts, which is unrelated. No such artefact exists to read."
        },
        {
          "candidate": "The pin-citation gate's corpus (check-objectui-pin-citations.ts)",
          "resolves_in_ci": "YES, but carries no measured reader facts",
          "how_measured": "Read the script header: it scans packages/spec/src prose for sha citations in an asserting vs historical spelling and compares them to .objectui-sha. It reads no objectui sources. Its corpus is `grep -rn '.objectui-sha` = ' packages/spec/src --include=*.ts` = 8 hand-written read-point records. Those DO name objectui file:line readers and are pin-fresh-gated, so the shape is tempting — but they are author-written prose, not measurement: the author who published the wrong deprecation would have written the record asserting a reader too. A guard reading them refuses nothing the author is willing to assert."
        },
        {
          "candidate": "The checked-in root sdui.manifest.json (found by measurement; not named on the card)",
          "resolves_in_ci": "YES, and pin-anchored — but does NOT contain the answer",
          "how_measured": "Tracked (`git ls-files --error-unmatch sdui.manifest.json`), landed by #13446, of exactly the shape a consumer would parse (components[type].inputs), and kept honest by scripts/check-sdui-manifest.mjs (absence/shape, sha256 tamper record, record objectuiSha == .objectui-sha) in the required lint job — so it survives the pin moving, the triage's stated requirement. But it is a DECLARATION corpus, and I measured it wrong in both directions: list-view declares inputs 'objectName, viewType, columns, filter, sort, options' — no 'data' — while ListView.tsx reads schema.data at ~15 sites (false negative on readers); and a declared input proves nothing about reads, which AGENTS.md already records for check:react-declaration-parity ('A prop both sides declare and no renderer reads is, to this gate, perfect agreement'). Building the guard here is the exact trap the triage warned about: a corpus that resolves but does not contain the answer."
        },
        {
          "candidate": "objectui's published @object-ui/* packages read offline from node_modules",
          "resolves_in_ci": "NO",
          "how_measured": "`git grep -c '\"@object-ui/' -- '*package.json'` = 0 files: nothing in the workspace depends on them. scripts/gen-sdui-manifest-node.mjs obtains them with a 'temp npm install (network)' (its own header, :8) — the network-in-a-required-lint-job shape that scripts/check-sdui-manifest.mjs's header explicitly declines, citing check-sdui-lockstep declining it for the same reasons."
        }
      ],
      "baseline_if_built": "The seat's proposed two-row baseline does not survive re-measurement at prop granularity. ListView.viewType -> 'type': UNREAD, confirmed — plugin-list has zero non-test reads of schema.type and zero of specType, and packages/components/src/renderers/layout/react-page.tsx:93 overwrites type with the block tag, parking an author 'type' as specType, which plugin-list never reads (plugin-charts does, at normalizeChartSchema.ts:277 — so specType has a reader in objectui, just not in this renderer; the seat's claim that specType is unread anywhere is too strong). ListView.objectName -> 'data': the prop IS read (schema.data, ~15 sites), so a prop-level guard is GREEN here and this row would NOT be baselined; only the prescribed shape provider:'object' is unread (zero hits). So a correct prop-level baseline has ONE row, not two, and the row the card names in its own criterion is not in it.",
      "open_questions": [
        {
          "question": "Which evidence source should the reader guard read, given that none of the candidates both resolves in the gate's CI job and carries measured reader facts?",
          "options": [
            "A. A dedicated CI job clones objectui at the pin and the guard greps its sources. Real business need: serves the open window, but the guarded population is rare spec-author edits. Long-term: puts a network clone inside a lint job — a shape this repo's own gate headers decline twice (check-sdui-manifest, check-sdui-lockstep) with stated reasons; attaching it to the existing console job instead inherits that job's cache-conditional dormancy. Anti-AI-error: strong if it runs, since it refuses at publish time. Startup focus: against — a new CI job plus a cross-repo network dependency to fence a window meant to be temporary. Also: triage 5523567239 says a repo-level CI gate outside the generator is domain:devx, i.e. a re-route, not this lane.",
            "B. objectui exports a per-component reads manifest (which schema paths its renderers actually read, including nested shapes like data.object); objectstack vendors it at pin bump exactly as sdui.manifest.json is vendored today. Real business need: the only shape that answers the question actually asked, and it generalises — the pin-citation header prices the current hand-derivation of cross-repo read points at a full dispatch cycle (#9397), so liveness sweeps would reuse it. Long-term: contract-first, and it copies a proven in-repo precedent (checked-in artefact + sha256 record + pin-freshness gate), offline per-PR, survives pin moves. Anti-AI-error: strongest — the declaration is refused unless a measured reader exists, with no consumer-side tolerance. Startup focus: highest upfront cost and cross-lane; justified only while the deprecation window stays open, or if the artefact earns its keep for liveness generally.",
            "C. Build on the checked-in root sdui.manifest.json. Real business need: cheapest, resolves today, no new artefact. Long-term: reuses a pin-anchored artefact, which is attractive, but it is a declaration corpus. Anti-AI-error: actively harmful — measured wrong in both directions above, so it would manufacture a false green on the very row the card cites and a false red elsewhere; a gate that reports 'verified' without verifying is the tolerance shape axis 3 forbids. Startup focus: cheap, but the cheapness buys a wrong answer. NOT RECOMMENDED.",
            "D. Build no reader guard; put the deprecation window itself back to the maintainer. Real business need: the name-validity half is ALREADY gated — packages/spec/src/ui/react-blocks.test.ts:164-165 asserts a replacedBy names a real prop on the block — so the residual exposure is one further unread replacement authored during the window. Long-term: fewest moving parts; the defect class disappears when the window closes. Anti-AI-error: weakest — nothing refuses a new unread replacement. Startup focus: strongest claim, maintainer 2026-08-27 verbatim 「项目在创业阶段,用户也很少,短期不考虑渐进。」 — deprecated spellings default to immediate retirement with no staged window, and #14791's B ruling keeps open exactly the window this guard would be built to fence."
          ],
          "recommendation": "B if a guard is wanted at all — it is the only shape that both resolves offline in the gate's own job and carries measured reader facts, and it is the only one that can express the shape-level fact that the incident actually turned on. But I do not recommend starting B before D's axis-4 question is settled by the maintainer, because B's cross-lane cost is only justified while the window stays open, and axis 4 argues the window should not. Explicitly not C: it resolves, which is what makes it dangerous. A belongs to domain:devx if it is chosen at all."
        },
        {
          "question": "At what granularity should the criterion bind — the replacement PROP, or the replacement SHAPE the deprecation note prescribes?",
          "options": [
            "Prop-level, as the card's executable criterion is literally worded. Measured consequence: green on replacedBy: 'data', because schema.data has ~15 read sites — the guard would not refuse the incident that motivated it.",
            "Shape-level: the guard checks that the spelling the note prescribes (data={{ provider: 'object', object }}) has a reader, i.e. that a provider === 'object' branch exists. Measured consequence: red on both existing rows today, which is the honest answer and matches the incident. Costs more from the evidence source: it needs nested-path reader facts, which only option B can supply."
          ],
          "recommendation": "Shape-level. Prop-level is measurably vacuous for the case on the card, and adopting it would ship a guard whose green means nothing — the same failure the card exists to end. This also raises the bar on the evidence source, which is part of why B is the recommendation above rather than a convenience."
        }
      ],
      "out_of_scope_findings": [
        "filed as #15367: check:react-declaration-parity refuses to run for want of a manifest that is now checked in at the repo root — its header and AGENTS.md both still assert 'this repository carries no manifest to fall back on', but a tracked, pin-freshness-gated sdui.manifest.json of the gate's expected shape has been at the repo root since #13446. No assignee, no labels, left for triage.",
        "not filed, reported here as it is inside this card's own surface: the seat's claim 5540747500 states specType is unread at the pin. It is unread in plugin-list (the ListView renderer), but plugin-charts/src/normalizeChartSchema.ts:277 does read it. The conclusion for this card is unchanged; the supporting fact needs narrowing to the renderer."
      ]
    }

    Generated by Claude Code

  7. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    Seat disposition — dev returned needs_decision; card moves to the decision inbox (2026-09-04T13:14Z)

    domain:spec seat, session session_01H2oQebDDxYKfWZusyd8GXk. The dev's report above (measurement only, no code, no PR; the empty probe branch claude/issue-14897-react-blocks-replaced-by-reader-guard is deleted by the seat) establishes two facts the seat accepts as measured:

    1. No named evidence source both resolves in the gate's CI job and carries measured reader facts. The spec gates run in lint.yml with "no network, no build and no objectui checkout" (:678-682); the only objectui clone is ci.yml's console-build step, cache-conditional on .objectui-sha — dormant on exactly the PRs that could add a replacedBy. No data-reads manifest exists in objectui at the pin. The pin-citation corpus is 8 author-written prose records. The checked-in sdui.manifest.json resolves offline but is a declaration corpus measured wrong in both directions (list-view declares no data input while ListView.tsx reads schema.data at ~15 sites).
    2. The card's executable criterion is vacuous on its own example. At prop granularity replacedBy: 'data' is GREEN (readers exist); the incident was shape-level — ListView handles provider 'value' / 'api' and has no 'object' branch, so the prescribed data={{ provider: 'object', object }} renders empty. A prop-level guard would not refuse the defect it was commissioned to prevent.

    Seat correction (public, on the claim 5540747500): the claim said specType is unread at the pin. It is unread in plugin-list (the ListView renderer); packages/plugin-charts/src/normalizeChartSchema.ts:277 does read it. The conclusion for this card is unchanged; the fact is narrowed to the renderer. The seat's proposed two-row baseline is also withdrawn — at prop granularity only viewType → type is unread; at shape granularity both rows are.

    State transition, same stroke: pm:dispatched → needs-user-decision (priority:p2, domain:spec, tooling kept; claim released, assignee cleared). The card's ## The construct and ## Executable criterion are superseded by the block below until the maintainer rules. Dev-filed finding #15367 (check:react-declaration-parity still refuses to run although the manifest it wants is checked in) is triage's.

    四棱分析(席出具,供裁决)

    一句话问题: react-blocks 的 deprecated.replacedBy 守卫要读什么证据,以及要不要建 —— 实测没有任何候选证据源同时满足「在门禁所在 CI job 可解析」与「携带实测读者事实」,而卡面判据(prop 级)在自己举的事故上是空的(data 有 15 处读者;缺的是 provider:'object' 这一形状)。

    选项 做什么 真实代价
    A 新 CI job 克隆 objectui 再 grep lint 类 job 里加网络克隆 本仓两处门禁头(check-sdui-manifest、check-sdui-lockstep)明写拒绝这个形状;挂到现有 console job 则继承其 cache 命中即休眠;且分诊已判「生成器之外的仓级 CI 门禁 = domain:devx」,是转道不是本席
    B objectui 导出「每组件实际读取路径」manifest,objectstack 随 pin bump 供入(照 sdui.manifest.json 先例:入库 + sha256 + pin 新鲜度门禁) 跨仓一个新产物 + 本仓一个守卫 唯一能回答「形状级读者」的方案,也是 liveness sweep 跨仓读点的通用答案(pin-citation 头把手工推导定价为一整个派发周期);代价是跨车道、前期最重
    C 直接读现有 sdui.manifest.json 零新产物,今天就能跑 声明语料,实测两个方向都错:会在卡面引用的那一行给出假绿、别处给假红 —— 「报了 verified 却没 verify」正是③轴禁止的宽容形状。不推荐
    D 不建守卫;把 deprecation 窗口本身交维护者 关卡 not planned;名字有效性一半已由 react-blocks.test.ts:164-165 钉住 剩余暴露 = 窗口期内再写一条无读者替代;窗口关闭(objectui#7477 落地)缺陷类自然消失;代价是「什么都不挡」

    推荐:D(关卡 not planned,理由记录:卡面判据实测空;名字有效性已有钉子;缺陷类随 objectui#7477 落地消失)。回退:B,仅当维护者认为跨仓「实际读取路径」manifest 值得为 liveness sweep 通用建设 —— 届时先在 objectui 立卡(Blocked-by: 本卡),本仓守卫按形状级判据重写(prop 级已实测空)。⛔ 不取 C;A 若取属 domain:devx。

    置信缺口: 未量化窗口期内 react-blocks 的 replacedBy 新增频率(守卫保护的事件基数);未测 objectui 侧维护 reads-manifest 的成本。


    Generated by Claude Code

  8. removed their assignment
    on Sep 4, 2026
  9. os-justin commented on Sep 4, 2026

    @os-justin
    Collaborator

    Correction to the disposition above (5540916705): the empty probe branch claude/issue-14897-react-blocks-replaced-by-reader-guard was not deleted — git push origin --delete from this session answers "Everything up-to-date" twice while ls-remote still lists the ref (the session's git proxy does not carry ref deletions). It stays as pushed by the dev: 0 commits ahead of main, no PR. Whoever executes the ruling may reuse or delete it.


    Generated by Claude Code

  10. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Maintainer ruling recorded — D: no reader guard is built; this card closes not_planned. needs-user-decision comes off in the same stroke.

    Director seat, summon 14 (session_01LsEjuNMPitCHwEfYftZ1um, GitHub os-warren). Provenance: maintainer, live PM chat, 2026-09-04, decision batch #36, verbatim 「同意」 on the presented recommendation 1(1A·2A) · 2B · 3D (this card is item 3). Premise carried from the dev's step-one measurement (5540886558) and the spec seat's disposition (5540916705); ⛔ not re-run by this seat.

    Why D, on the record

    Fallback recorded, not taken

    B (objectui exports a per-component reads manifest, vendored at pin bump like sdui.manifest.json; a shape-level guard here) is the only shape that answers the question honestly, and it is worth building only if a cross-repo reads manifest is wanted for liveness sweeps generally. If the maintainer ever wants that, it starts as an objectui card and this card reopens Blocked-by: it with the criterion rewritten at shape granularity. A (a CI job cloning objectui) is domain:devx and a shape this repo's gate headers refuse twice.

    State

    needs-user-decision off; closed not_planned. The empty probe branch claude/issue-14897-react-blocks-replaced-by-reader-guard (0 commits ahead) is whoever's to delete — ⛔ not this seat's (deleting another tool's branch is a destructive action). #15367 (dev-filed: check:react-declaration-parity refuses to run although its manifest is checked in) is triage's.

    Ruling authority: maintainer 2026-09-04 (batch #36). Related: #14791 · #11284 · PR #11695 · objectui#7477 / PR #7628 · #15367.


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions