Repository navigation
fix(spec): os migrate meta --stored lists a stored dashboard that repeats a global filter key - #29399
Conversation
…in the v18 stored listing The duplicate globalFilters key refusal had no RefusedStoredShape, so `os migrate meta --stored` named no stored dashboard the parse refuses. The detector calls duplicateGlobalFilterNames, the refinement's own predicate, and reports at the site the parse locates. Claude-Session: https://claude.ai/code/session_01ADHkmQfSq34mnnkWb6STfV Co-authored-by: Claude <noreply@anthropic.com>
…al filter key refusal in every mode PUT /api/v1/meta/dashboard answers 422 INVALID_METADATA at globalFilters.1.name with no mode, publish and draft, and stores nothing. Plus the patch changeset for the stored listing. Claude-Session: https://claude.ai/code/session_01ADHkmQfSq34mnnkWb6STfV Co-authored-by: Claude <noreply@anthropic.com>
…the engine-double ledger Claude-Session: https://claude.ai/code/session_01ADHkmQfSq34mnnkWb6STfV Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 168 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 56f21c7480710d3536df4a02f62f50d852d1694d && git checkout 56f21c7480710d3536df4a02f62f50d852d1694d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dfbe34d8874326be643042c36211ba6ec5e69c7f 0b17ca15d13017ee0fa2303909d430cd3ef43f36 && git checkout -B drift-repro dfbe34d8874326be643042c36211ba6ec5e69c7f && git merge --no-ff 0b17ca15d13017ee0fa2303909d430cd3ef43f36
node scripts/docs-audit/affected-docs.mjs --json dfbe34d8874326be643042c36211ba6ec5e69c7f |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 0b17ca1 · 2026-11-02T14:07Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs read: card #29212 body and its three comments (triage 6574212312, claim 6576712044, os-dev-report 6579168801); PR #29399 body, file list (5 files) and the net diff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地。契约复审在现 head 判 PASS,CI 全绿。 席位核对 — ACCEPT ·
|
Requested by Steve · project thread
Fixes #29212
Clause-②: no
Metadata-protocol: none
维护者速读
os migrate meta --stored现在会列出「同一个看板里两个全局筛选器用了同一个 key」的已存数据行;另加一个测试钉住保存接口在三种模式下都拒收这种看板。packages/spec/src/conversions/refused-shapes.ts(新 detector,调用同一个判定函数)、packages/spec/src/conversions/refused-shapes.test.ts、packages/metadata-protocol/src/protocol.dashboard-duplicate-global-filter-name-refusal.test.ts。pnpm --filter @objectstack/spec exec vitest run src/conversions/refused-shapes.test.ts,预期 83 个测试全过,其中 spec(dashboard): the duplicate globalFilters key refusal (#29023) has no stored-row detector and no save-door pin #29212 三条。What changes
REFUSED_STORED_SHAPESgainsdashboardDuplicateGlobalFilterName(idui-dashboard-global-filter-duplicate-name-refused, the dashboards: two globalFilters with the same name are accepted; the board draws one control and its value filters the other filter's field #29023 D3 entry of step 18). It walksstack.dashboards[]and callsduplicateGlobalFilterNamesfromui/duplicate-global-filter-names.ts, the refinement's own predicate; no logic is copied. One TODO per repeated key, atdashboards[i].globalFilters[j].name(or.fieldwhen the second declaration has noname), which is the site the parse refuses.fromcarries the key and the indexes only. The reason leads with the fate (no lossless rewrite, loads with_diagnostics.valid: false, next save refused 422) and ends with the predicate's own message.packages/metadata-protocol).saveMetaItem({ type: 'dashboard', … })with two filters namedranswers422 INVALID_METADATA, onecustomissue atglobalFilters.1.name, and stores nothing, with no mode,mode: 'publish'andmode: 'draft'; a distinct-name control saves in each mode. The harness is the object sibling's (protocol.object-duplicate-action-name-refusal.test.ts), copied.scripts/engine-double-contract.pinned.jsongains the new file's three pinned rows (delete,findOne,update), written bycheck-engine-double-contract.mjs --write. Not a forced test edit; it is the gate's own recipe for a new pinned double..changeset/29212-dashboard-duplicate-filter-stored-listing.md,@objectstack/specpatch.No forced test edit: the only existing assertion touched is the refused-shape roster pin in
refused-shapes.test.ts, which names each registered id by design ("A new entry is a reviewed edit").Tests
refused-shapes.test.tsgains three cases under#29212: the card shape (two filters namedr) gives one TODO atdashboards[0].globalFilters[1].namewith the key, both indexes and the predicate's message, the row unrewritten, and the parse still refusing the same site; a key that falls back tofield(three entries,regiontwice unnamed plus one namedregion) is listed once at.fieldwith indexes[0, 1, 2], as the parse locates it; distinct keys and distinct names over onefieldare not listed and the parse accepts them. The roster pin names the new id.protocol.dashboard-duplicate-global-filter-name-refusal.test.ts: 6 cases (refusal + control, in each of no mode,publish,draft), 6 passed.scripts/ablation-replace.mjs --deleteon the registry linedashboardDuplicateGlobalFilterName,): Tests 3 failed | 80 passed (83), the roster pin and the two listing cases red, the not-listed control green; restored, blob == HEAD,git diff HEADempty. The spec suite imports./refused-shapes.jsfrom source, so no dist was involved. Two earlier attempts with--replacementwere refused by the script as no-ops (the replacement text already occurred), so they mutated nothing.Package readings at head 0b17ca1 (after merging main)
pnpm --filter @objectstack/spec test: Test Files 791 passed (791), Tests 23394 passed | 1 todo (23395).pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date.pnpm --filter @objectstack/metadata-protocol test: Test Files 304 passed | 4 skipped (308), Tests 22142 passed | 26 skipped.pnpm --filter @objectstack/cli exec vitest run --project unit src/commands/migrate/meta.report-order.test.ts(themigrate metareport over stored dashboards): 21 passed. No CLI test lists refused-shape ids; the rest of the CLI suite is left to CI.b057b3f2: spec typecheck exit 0, metadata-protocol typecheck exit 0, the same suites green (spec 23394, metadata-protocol 22133).Acceptance notes
origin/main@5a2bdf9cwith the spec dist built:applyConversionsToStoredItem('dashboard', row, { onTodo })for a row with two filters namedrhanded 0 TODOs; the control, an object with two actions nameda, handed 1 (object-duplicate-action-name-refused).premise_still_valid: true.acceptanceCriteria(18.ui-dashboard-global-filter-duplicate-name-refused.ts) does not yet say the row is listed, unlike its object sibling's. Still true as written, so it is left alone: it sits outside the claimed file surface.domain:serverclaim holds apackages/metadata-protocol/src/protocol.*duplicate*file: the 23 openpm:dispatcheddomain:servercards were read, and none names one ([maintainer] refactor(metadata-protocol): splitsrc/protocol.ts(33,065 lines, +10,169 in 30 days) along itsObjectStackProtocolImplementationseams, one move-only slice per PR #25467 is the move-onlyprotocol.tssplit, not a test file).duplicate-global-filter-names.tsanddashboard.zod.tsare untouched (sibling spec(dashboard): a globalFilters entry nameddateRangeparses and silently replaces the built-in date range's binding #29213). If spec(dashboard): a globalFilters entry nameddateRangeparses and silently replaces the built-in date range's binding #29213 changes the predicate's return shape, this detector follows it on a main merge.dateRangebeside a declareddateRange, PR fix(spec)!: refuse a dashboard globalFilters entry keyed dateRange beside a declared dateRange #29370, still a draft when this PR opened). Its stored-row listing goes to the seat as a follow-up and is not built ahead of the predicate it would call.Verification
Head
0b17ca15(one merge oforigin/main, 27 commits, after round 1 onb057b3f2).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 86 families from 5 paths (223 changed lines), the same list on both heads; each ran after the last commit with its exit code recorded, and--rananswered on each head: 86 derived, 86 run, 0 NOT-MEASURED, 0 UNRUN (exit 0).Gate notes
pnpm check:engine-double-contractfirst asked for--write(the new file's three pinned doubles); the ledger rows are committed and the gate answers exit 0.check:objectql-double-limitandcheck:where-matcherexit 0 with no new file named.pnpm check:dual-build-cjs-loadsfirst answered exit 3 (PREREQUISITE NOT MET, ten packages with nodist/); after building them it answered exit 0 (115 entry points across 70 packages). On the merged head it answered exit 0 directly.node scripts/check-plugin-teardown-shape.mjs --self-test: the pinned control commit was fetched up front (git fetch --depth=1 origin 621a4876); exit 0.pnpm exec eslintover the three changed source and test files with--no-inline-config: exit 0. The repo-widepnpm lintis CI's.node scripts/check-changeset-no-major.mjs --base 57cde8cb --event: exit 0, level axisno/noneatpatch.check-partof-closing-keyword.mjs: exit 0.Lint & Repo Gates,TypeScript Type Check,Test Core,Build Core, the UI jobs) are read from this PR's check-runs, not re-run here. Dogfood is not owed: no!in the title and no(narrowing)arm.Generated by Claude Code