Repository navigation
feat(spec)!: a joined report refuses a block that binds no dataset, at blocks[i].dataset, naming the block (#21702) - #21712
Conversation
…t blocks[i].dataset The joined arm of ReportSchema's refinement now enforces the 'each block dataset-bound' contract the schema comment and the reports guide already state. ADR-0087 D3 entry and step-18 rationale fragment. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…to a registered dataset A joined report's block with no dataset is now refused at blocks.0.dataset, so the fixture that left its block unbound (to dodge chart-dataset-unknown) binds it and the harness registers the dataset it names. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…the joined block dataset refusal Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 77436cf1a55e418453affda9ebf9015e46ff1d64 && git checkout 77436cf1a55e418453affda9ebf9015e46ff1d64
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7e0066af7a8e05709dc60096c69bc85e299d0331 c4e3ab96316d9a1f46f827781938a1b5f0843a32 && git checkout -B drift-repro 7e0066af7a8e05709dc60096c69bc85e299d0331 && git merge --no-ff c4e3ab96316d9a1f46f827781938a1b5f0843a32
node scripts/docs-audit/affected-docs.mjs --json 7e0066af7a8e05709dc60096c69bc85e299d0331
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37194495975 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #21702
Clause-②: yes (narrowing)
Triage direction
5977861131(enforce), claim5977924610. The joined arm ofReportSchema's refinement now refuses each block of ajoinedreport that binds nodataset, atblocks[i].dataset, naming the block, with the prescription to bind the block to a dataset. The refinement comment "each block dataset-bound" and the reports guide's type-table cell "(each block dataset-bound)" were declarations; they are now enforced.What changes
The refusal (
packages/spec/src/ui/report.zod.ts). A per-block arm inside the joined branch ofReportSchema'ssuperRefine, right after the existing "needsblocks" check. For each block whosedatasetis undefined it adds onecustomissue at['blocks', i, 'dataset']. The message comes from a module-private builder,joinedBlockDatasetRequired, worded like the neighbouring joined refusals:NAMEis the block'sname. A block whosenameis empty (its owntoo_smallissue, which does not stop the refinement) is named by position instead, asblocks[1].What stays accepted.
JoinedReportBlockSchema.datasetstays.optional():blocksis read on ajoinedreport only, so the requirement lives on the joined arm, and a block on a non-joined report is not judged (pinned). No type, export or JSON Schema key changes. The block'sdataset.describe()now reads "Dataset name to bind (ADR-0021); a joined report refuses a block without one", andcontent/docs/references/ui/report.mdxis regenerated withgen:docs(two table rows).The ADR-0087 kit, in feat(spec)!: FlowSchema refuses a create_record, update_record or delete_record node whose static objectName is a stored-metadata table, with the runtime's prescription (#21654) #21687's shape:
entries/semantic/18.ui-report-joined-block-dataset-required.ts;STEP18_RATIONALEat order 77, inserted where its id sorts. 77 is the next free order: the highest onmainis 76, re-read at7e0066af7ajust before this PR. feat(spec)!: object-gantt markers, object-timeline mapping and both forms' fields take the shape each block reads; five objectui-held contracts reported as forks (#21464, S-objectui-held) #21699 (74 to 76) and fix(spec): the protocol 17 → 18 upgrade rationale states each cited decision in words instead of a tracker number (stage 9) #21698 landed before this branch's base16d241a6af, so no merge was needed;registry.tsregenerated withgen:migration-registry;@objectstack/specminorchangeset,.changeset/21702-joined-report-block-dataset-required.md, carrying theClause-②line, theadr-0087: registered ui-report-joined-block-dataset-requireddisposition marker and a FROM → TO table.The fragment and entry text state each decision in words and carry no tracker number. No tombstone (no key is removed) and no D2 conversion (only the author knows which dataset a block was meant to show).
content/docs/ui/reports.mdx: no edit. No sentence there became false; the type-table cell became true. See Acceptance notes.Census, at base
16d241a6af, before any editgit grepfor atype: 'joined'report acrossexamples/**,packages/**,skills/**,content/docs/**,docs/**,scripts/**andapps/**gave 33 lines in 15 files: CHANGELOG quotations, the spec's own comments, and these reports:examples/app-showcaseTaskOverviewReportcontent/docs/ui/reports.mdxexamplepackages/lintvalidate-chart-bindings.test.ts(raw stacks, never parsed)packages/platform-objectsreport-form-echo-decisions.test.tspackages/spectests (report.test.ts,filter-save-door-face-parity.test.ts,joined-report-block-type.test.ts) and thereport-joined-chart-removedconversion fixturepackages/metadata-protocolprotocol.invalid-metadata-422-face-inventory.test.tsskills/**Triage measured hotcrm
4054ec26(1 joined report, 0 unbound blocks) and cloud2205b530(none). I took those readings as given. I read hotcrm'ssrc/sales/reports/churn.report.tsonce, to shape the preservation fixture. Every block binds a dataset: three bindaccount_metricsand the fourth,recently_closed_lost, bindsopportunity_metrics. So triage's line "every block bindsaccount_metrics" is slightly off; its conclusion, zero unbound blocks, stands.objectui (read only, at
2e818d0b51and at this repo's pinab18797215). Studio's joined-report authoring can save a block with nodataset. That producer is this narrowing's reach:ReportDefaultInspector.tsxrenders the blocks throughSchemaFormwith the specreportForm"Joined blocks" repeater;RepeaterField'sadd()seeds a blank row with every columnundefined;datasetcolumn is free text and not required (the block's derived JSON Schemarequiredis["name"], measured);ReportSchema(clientValidation.ts) and the save door both accepted the result.Filed as Studio report inspector: a
joinedreport'sblocksrepeater saves a block with nodataset, which objectstack's ReportSchema now refuses atblocks.N.datasetobjectui#11601: category ①, no labels, dedupe query and hit count in its body. After the spec bump, Studio's live validation and the save door both refuse such a block atblocks.N.dataset.The renderers, at the pin:
DatasetReportRenderer's joined branch passesString(block.dataset ?? '')to each block's table, and that table's query hook goes idle on an empty name (:443). A report whose blocks all lack one failsisDatasetReportand falls through to the presentation bridge.DrillDownDrawer'sisDatasetBoundReportlists the records instead of drawing it.Fixture triage: one test-only file outside the claim's file surface
protocol.invalid-metadata-422-face-inventory.test.tssection 5 (the joined-reportchartdoor pins) left its block unbound on purpose: the door's author-time gate refuses an unresolvable dataset withchart-dataset-unknown, and the stub engine had no dataset universe. The new refusal turned 2 of its 23 tests red (Tests 2 failed | 21 passed): the container case got a second issue atblocks.0.dataset, and the CONTROL was refused atblocks.0.dataset. That second red is the metadata save door (422 INVALID_METADATA,writeFace: 'meta-envelope') refusing the probe shape.task_metrics, and the harness gains an optionalmakeProtocol({ datasets }), which registers that dataset throughregistry.listItems('dataset'). Every other caller passes nothing, so the registry lists nothing, as before. Result:Tests 23 passed (23).makeProtocol()without datasets (scripts/ablation-replace.mjs, anchor 1 → 0, blobf718099c2d→9dc51bd7b8). Predicted 1 red / 22 green; observedTests 1 failed | 22 passed. The failure waschart-dataset-unknownatreports[0].blocks[0].dataset, "Declared datasets: (none)". Restore: blob back tof718099c2d, which equals HEAD, andgit diff HEADis empty.body, including one with neither abodynor ahandler(#21689) #21706, queued at this writing, appends a section to the same file at line 570 and later. A localgit merge-treeof this head with that queue head exits 0. The file is notmerge=os-regenrouted, so that local answer is also GitHub's.Doors, tested and probed
packages/spec/src/ui/report-joined-block-dataset.test.ts, 16 tests):blocks.0.datasetandblocks.1.dataset, each naming its block; a bound block beside an unbound one draws one issue, at the unbound index; an empty name is named by position;JoinedReportBlockSchemaparsed on its own, both still parse;defineReportthrows; the registeredreporttype schema refuses (and accepts the bound report);ObjectStackDefinitionSchema, the stack parseobjectstack validateruns, refuses atreports.1.blocks.{0,1}.dataset;defineStackanswersSTACK_SCHEMA_INVALID/ 422;TaskOverviewReport(mirrored byte for byte) parses with output equal to its input, and a fixture shaped like hotcrm'scustomer_churn_signalsparses, gaining only thedrilldowndefault;JoinedReportBlock:datasettombstone, with a control on a known tombstone.objectstack validate, the real CLI (packages/cli/bin/run.js, built here). Fixtures were temporary, in the session scratchpad, outside the repo;@objectstack/specresolved to this worktree's build.name+label): exit 1,"code": "STACK_SCHEMA_INVALID",defineStack validation failed (2 issues)atreports.0.blocks.0.datasetandreports.0.blocks.1.dataset, each with the message above."valid": true.examples/app-showcase/objectstack.config.ts, four reports includingTaskOverviewReport): exit 0,"valid": true.dist. The CLI reads@objectstack/specthroughdist, so the mutation was rebuilt:ctx.addIssueof the new arm became aglobalThismarker assignment (anchor 1 → 0, blobfadd536165→2cbfdb9feb);@objectstack/specrebuilt;ablation-dist-preflight.mjsfound the marker indist(exit 0); the probe then gave exit 0,"valid": true. That reproduces the card's reading atmain;fadd536165, which equals HEAD, andgit diff HEADis empty; rebuilt;--absentfound the marker in none of 228 built files (exit 0); the probe gave exit 1STACK_SCHEMA_INVALIDagain.Ablation of the refusal, at the pins (predicted first)
scripts/ablation-replace.mjsreplaced the arm'sctx.addIssue(...)with a no-op (anchor 1 → 0, blobfadd536165→45f34370c5). The pins import./report.zodrelatively, which resolves tosrc, so no build was needed. Predicted: 8 red, the 4 refusal pins and the 4 door pins; 8 green, the advice, the 2 controls, the 2 preservation pins and the 3 ledger pins;report.test.tsuntouched. Observed:Tests 8 failed | 80 passed (88)over the pin file andreport.test.ts. The 8 were exactly the predicted ones. Restore: blob equals HEAD (fadd536165), andgit diff HEADis empty.Verification (all at head
c4e3ab9631unless noted)@objectstack/specfull suite (vitest run --project local --maxWorkers=2):Test Files 613 passed (613),Tests 18201 passed | 1 todo.pnpm --filter @objectstack/spec typecheck(tsc, scripts, test layer:check:test-typecheck: OK) andpnpm --filter @objectstack/metadata-protocol typecheck: exit 0.--listFilesshows both edited test files in their programs.Consumers that parse a joined report:
platform-objectsreport-form-echo-decisions.test.ts35 passed;lintvalidate-chart-bindings.test.ts47 passed;@objectstack/example-showcasewhole suite32 files, 399 passed;metadata-protocolface-inventory 23 passed.pnpm --filter @objectstack/spec check:generated: one artifact stale before regeneration (content/docs/references/**), regenerated withgen:docs; thencheck:generatedexit 0.Derived gates:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 115 commands. Every one was run, with its exit code recorded before any pipe.--ranreconciled 115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN.dist):check:skill-examplesandcheck:dual-build-cjs-loads. Both were re-run green after building those packages. All 115 exited 0.check:adr-0087-registration,check:changeset-no-major,check:migration-registry,check:spec-changes,check:upgrade-guide,check:authorable-surface,check:api-surface,check:docs,check:doc-authoring,check:issue-citations,check:nul-bytes,check:cross-package-test-inputs.eslint, narrowed and proven:
eslint.config.mjs's**/*.{ts,…}block covers all five changed.tsfiles;--format json: 5 files, 0 errors, 0 warnings;parserOptions.project, as its own comment states), so this diff cannot move a verdict on an untouched file.The repo-wide
pnpm lintis CI's.Declared to CI: the path-scheduled jobs (Test Core shards, Dogfood, Temporal Conformance, Build Core), the whole-workspace type-check lanes and every downstream consumer suite of
@objectstack/specbeyond the four above.Acceptance notes (observations, not filed)
reports.mdxsays ajoinedreport "must declare at least one block". That is still true, but it no longer names every requirement: each block must now also bind adataset. Not edited, under the claim's rule that only a sentence this change makes false is edited. Carrier: none.reportForm(packages/spec/src/ui/report.form.ts, "Joined blocks" repeater) offersdatasetwith noref:datasetwidget and norequired. Studio report inspector: ajoinedreport'sblocksrepeater saves a block with nodataset, which objectstack's ReportSchema now refuses atblocks.N.datasetobjectui#11601 names this as a possible spec-side fix for the Studio producer, to be measured first. Carrier: Studio report inspector: ajoinedreport'sblocksrepeater saves a block with nodataset, which objectstack's ReportSchema now refuses atblocks.N.datasetobjectui#11601's acting seat.ui/Reportalready sits indropped-refinements.baseline.json, and the new arm adds no site there. The.describe()text is now the JSON-Schema-visible hint.Generated by Claude Code