Repository navigation
[finding] The react-blocks generator publishes a deprecated.replacedBy whose replacement no renderer reads — nothing refuses it #14897
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 3, 2026 Triage — graded.
tooling·priority:p2·pm:queue·domain:spec;findingremoved. ⭐ objectstackfindingbox → 1 remaining this round.domain:specbecause the guard's natural home is the generator inpackages/spec/src/ui/react-blocks.ts— what it publishes is the thing being constrained.⚠️ The card's alternative (a gate besidecheck:react-blocks-*) would bedomain: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, offneeds-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
replacedBywould 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-shapinned checkout, adata-readsmanifest 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
Claim:
domain:specexecution seat, sessionsession_01H2oQebDDxYKfWZusyd8GXk, branchclaude/issue-14897-react-blocks-replaced-by-reader-guard. Labelspm:queue→pm:dispatchedwritten in one stroke with the assignee (2026-09-04T12:56Z;tooling,priority:p2,pm:dispatched,domain:spec). Nextpriority:p2card by age after #14766 / #14788; slot freed by #14723's PR #15363 passing review.Surface (read on
origin/main19be9dfcd, 2026-09-04T12:55Z): the generatorpackages/spec/src/ui/react-blocks.ts—deprecated?: { replacedBy: string; note: string }(:52), the two live rows on the ListView block (:275replacedBy: 'data',:285replacedBy: 'type'); the validatorpackages/lint/src/validate-react-page-props.ts:1184treating a usedreplacedByas satisfying the required prop; the react-blocks generator/gates underpackages/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/srcreadsschema.objectName(control: 6 files);data.object/specTypehits are only inapp-shell/src/views/metadata-adminanchors/default-schemas, not in the ListView renderer ⇒ both existingreplacedByrows are unread today. Seat ruling (third tier, falsifiable — the dev re-measures): a strict guard would be red onmainat 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 --tierno path-derived mandate; card content: a generation-time guard, no published contract member changes ⇒ Clause ② no; dispatched atopus. If the measurement shows the generator cannot see the consumer side from this repo in CI, the dev returnsneeds_reroute(triage's note 5523567239: a gate besidecheck:react-blocks-*isdomain:devx) orneeds_decisionwith the four-axis frame — ⛔ not an invented evidence source.Serial check (2026-09-04T12:55Z): no open PR touches
react-blocks.tsor the react-blocks scripts (PR #15284 in the queue regenerates only theskills/objectstack-ui/references/react-blocks.mdregister; a guard that refuses does not change generated outputs while the baseline holds).ui/react-blocks.tsis 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
Dispatch — round R3 slot refill, 2026-09-04T12:58Z
Seat
domain:spec, sessionsession_01H2oQebDDxYKfWZusyd8GXk,os-devsubagent atopus(Clause ② no — see the claim), branchclaude/issue-14897-react-blocks-replaced-by-reader-guard, treeorigin/main19be9dfcd(2026-09-04T12:55Z). Triage grading 5523567239 applies:domain:specbecause 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.tschecks sha citations, it does not read objectui sources; nodata-readsmanifest 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_decisionwith the four-axis frame below applied to the candidate sources (including "objectui exports adata-readsmanifest" as a cross-lane card), orneeds_reroutetodomain:devxif 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
00d3f09cobjectui's ListView renderer readsschema.objectNameand neitherdata.objectnorspecType(the seat's grep:data.objecthits only inapp-shell/src/views/metadata-admin), so a strict guard is red onmaintoday. 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 unreadreplacedByexits 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/besidebuild-react-blocks-contract.ts/check-react-blocks-declaration-parity.ts, or wherever (a) resolves), its--self-testproving the red case (a synthetic unread replacement) and the green case, the baseline file, wiring into the speccheck:*scripts and the CI job it can run in,content/docs/ script-header prose describing it; thedeprecateddocblock inpackages/spec/src/ui/react-blocks.ts:44-52gains one sentence pointing at the guard. Changeset:skip-changesetis wrong if a published package's scripts change behaviour for consumers — measure withcheck-empty-changeset/ the changeset gate; a tooling-only change inpackages/spec/scriptsthat ships nothing indisttakes the repo's tooling convention (say which gate line decided).Out of scope (⛔): removing or altering the two existing
replacedByrows (that is #14791 step 3, reserved, blocked on objectui#7477); any edit topackages/lint/src/validate-react-page-props.tssemantics; objectui edits;.objectui-shabumps.② 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; nevergit 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 --commandson your change set; run every derived command; real output lines;check:generatedmust 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 bygit 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-reportJSON: 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;块 md522f2339f0acb64cdb50c7adc9db681c3,SKILL.md## 升级与决策atorigin/main19be9dfcd)每个方案必须沿四条固定评估轴分析,这是决策分析的核心原则,不是可选项:
- 实际业务需求 — 它服务的是真实存在的业务场景,
还是投机性能力面?判据要求实测(谁在写这个键、谁在读、
示例应用与真实部署的用法),「读起来像有用」不作数。这条轴会改变结论,不是陪衬。 - 项目长远合理性 — 哪个方案符合北极星方向与可持续架构(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
Claim:
os-dev, sessionsession_01H2oQebDDxYKfWZusyd8GXk, branchclaude/issue-14897-react-blocks-replaced-by-reader-guard, worktree/home/user/objectstack-14897cut--no-trackfromorigin/mainatdc46c4ec1(main had moved past the dispatch's19be9dfcd). 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 bygit show origin/main:.objectui-sha(the shared checkout was stale at67dadd60; the pin the dispatch names is the live one). The tworeplacedByrows are where the claim says:packages/spec/src/ui/react-blocks.ts:275(objectName→data) and:285(viewType→type), blockListView,schemaType: 'list-view'. The docblock is at:38-52. Nothing refuses either row today — confirmed:replacedByappears inbuild-react-blocks-contract.tsonly as a type member (:69), and the sole existing assertion ispackages/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 granularityreplacedBy: 'data'is green, because objectui's ListView renderer readsschema.dataat ~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 theos-dev-reportbelow.
Generated by Claude Code
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
Seat disposition — dev returned
needs_decision; card moves to the decision inbox (2026-09-04T13:14Z)domain:specseat, sessionsession_01H2oQebDDxYKfWZusyd8GXk. The dev's report above (measurement only, no code, no PR; the empty probe branchclaude/issue-14897-react-blocks-replaced-by-reader-guardis deleted by the seat) establishes two facts the seat accepts as measured:- No named evidence source both resolves in the gate's CI job and carries measured reader facts. The spec gates run in
lint.ymlwith "no network, no build and no objectui checkout" (:678-682); the only objectui clone isci.yml's console-build step, cache-conditional on.objectui-sha— dormant on exactly the PRs that could add areplacedBy. Nodata-readsmanifest exists in objectui at the pin. The pin-citation corpus is 8 author-written prose records. The checked-insdui.manifest.jsonresolves offline but is a declaration corpus measured wrong in both directions (list-view declares nodatainput whileListView.tsxreadsschema.dataat ~15 sites). - 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 —ListViewhandlesprovider'value'/'api'and has no'object'branch, so the prescribeddata={{ 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
specTypeis unread at the pin. It is unread inplugin-list(the ListView renderer);packages/plugin-charts/src/normalizeChartSchema.ts:277does 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 onlyviewType → typeis unread; at shape granularity both rows are.State transition, same stroke:
pm:dispatched→needs-user-decision(priority:p2,domain:spec,toolingkept; claim released, assignee cleared). The card's## The constructand## Executable criterionare superseded by the block below until the maintainer rules. Dev-filed finding #15367 (check:react-declaration-paritystill 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 落地)缺陷类自然消失;代价是「什么都不挡」 - ① 项目长远合理性(≥50%):B 是 contract-first、离线可复验、pin 移动后仍成立的唯一形状,而且它的产物不只服务这张卡。A 把网络塞进 lint job,本仓已两次明文拒绝。C 是错误的语料。D 让缺陷类随窗口关闭而消失,零新机制。
- ② 实际业务拉动:守卫保护的人群 = 窗口期内改 react-blocks 的 spec 作者,罕见;事故本身已由 [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecates
objectName/viewTypeon ListView in favour ofdata={{ provider: 'object', object }}/type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791/objectui#7477 走消费者半边修复。B 的通用拉动(liveness 跨仓读点)是真的但不是本卡的拉动。 - ③ 防 AI 犯错:B 最强(声明无实测读者即拒绝,形状级);A 有效但常休眠;C 有害(假绿);D 最弱(不挡)。
- ④ 创业阶段不扩散:D 最强 —— 维护者 2026-08-27 逐字「项目在创业阶段,用户也很少,短期不考虑渐进。」:废弃拼写默认立即退休、不设窗口;这张卡要围的正是一个按该原则不该存在的窗口。B 前期最重且跨车道,只在窗口长期存在或产物服务 liveness 时才值。
推荐: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
- No named evidence source both resolves in the gate's CI job and carries measured reader facts. The spec gates run in
Correction to the disposition above (5540916705): the empty probe branch
claude/issue-14897-react-blocks-replaced-by-reader-guardwas not deleted —git push origin --deletefrom this session answers "Everything up-to-date" twice whilels-remotestill lists the ref (the session's git proxy does not carry ref deletions). It stays as pushed by the dev: 0 commits ahead ofmain, no PR. Whoever executes the ruling may reuse or delete it.
Generated by Claude Code
Maintainer ruling recorded — D: no reader guard is built; this card closes
not_planned.needs-user-decisioncomes off in the same stroke.Director seat, summon 14 (
session_01LsEjuNMPitCHwEfYftZ1um, GitHubos-warren). Provenance: maintainer, live PM chat, 2026-09-04, decision batch #36, verbatim 「同意」 on the presented recommendation1(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
- The card's executable criterion is vacuous on its own example. At prop granularity
replacedBy: 'data'is GREEN — objectui's ListView readsschema.dataat ~15 sites at the pin. The incident was shape-level (provider: 'object'has no branch). A guard built to the card's wording would not refuse the defect it was commissioned to prevent. - No evidence source resolves where the gate would run. The spec gates run with no objectui checkout; the console-build clone is cache-conditional on
.objectui-shaand dormant on exactly the PRs that could add areplacedBy; no reads manifest exists in objectui; the pin-citation corpus is author-written prose;sdui.manifest.jsonis a declaration corpus measured wrong in both directions. ⛔ C (build on it) is refused explicitly: a gate that reports "verified" without verifying is the tolerance shape the third axis forbids. - The name-validity half is already pinned —
packages/spec/src/ui/react-blocks.test.ts:164-165asserts areplacedBynames a real prop on the block. What remains unguarded is one further unread replacement authored during the deprecation window. - That window is closing. [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecates
objectName/viewTypeon ListView in favour ofdata={{ provider: 'object', object }}/type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791 step 3 (remove the two deprecation rows) wasBlocked-by: objectui#7477, whose PR QA run · cli (FULL area) · 92f26f75 · 2026-08-11 · 4 PASS / 1 PARTIAL / 1 FAIL #7628 merged today at 09:23Z; [finding] The #11284 convergence shipped only its producer half: the react-blocks contract deprecatesobjectName/viewTypeon ListView in favour ofdata={{ provider: 'object', object }}/type, and the lint blesses that spelling — but objectui's ListView reads neither, so the canonical spelling validates green and renders an empty list #14791 is returned topm:queuein this stroke. Per the maintainer's 2026-08-27 principle (「短期不考虑渐进」), a deprecated spelling retires rather than living behind a fence.
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 reopensBlocked-by:it with the criterion rewritten at shape granularity. A (a CI job cloning objectui) isdomain:devxand a shape this repo's gate headers refuse twice.State
needs-user-decisionoff; closednot_planned. The empty probe branchclaude/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-parityrefuses 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
- The card's executable criterion is vacuous on its own example. At prop granularity
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.tspublished a ListView block whoseobjectNameisrequired: trueanddeprecated: { replacedBy: 'data' }, withviewTypereplacedBy: 'type', andpackages/lint/src/validate-react-page-props.ts:1132-1135treats a presentreplacedByas satisfying the required prop. objectui's ListView read neither replacement (objectstack#14791, measured:data.object0,specType0,schema.objectName52). 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'sreplacedByis prose to the generator.The construct
At generation (or as a gate beside
check:react-blocks-*): adeprecated.replacedByon 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, adata-readsmanifest 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_issuesonreplacedBy/deprecated.replacedBy/react-blocks generator refuseat filing: #14791 (the incident) and #11284 (the ruling) only; no card records the missing guard.Refs: #14791 · #11284 · PR #11695 · objectui#2890.