Skip to content

dashboards: two globalFilters with the same name are accepted; the board draws one control and its value filters the other filter's field #29023

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbX9avDwM2goWd7zimE

Filing gate: ① a product defect, found beside the clauses (dashboards.global-filters-rescope), reach: PUT /api/v1/meta/dashboard and the console board, measured twice.
Business reason: two global filters that share a name (easy to do when one is copied) are accepted. The board draws one control, and a value typed into it filters the field of the other definition, so the reader gets a silently wrong number on a tile. The save door already refuses an unknown filter binding (dashboard-filter-binding-unknown); a duplicate name is the same class.
Reader who acts: the triage seat, then the domain:server lane (packages/lint/src/validate-widget-bindings.ts or GlobalFilterSchema in packages/spec). QA suggestion only: area:dashboards, p3.
Duplicate search: duplicate global filter name dashboard → 79 hits, all QA run records, the sweep anchor, or unrelated.
Batch-of: release-18.3.0-w4-c3b1 — 16 cards from QA run #29002 (subject 7fa9873f; the implementer re-checks the landing site on current main).

Reproduction

  1. PUT /api/v1/meta/dashboard/qa_probe_dfn with globalFilters:[{"name":"r","field":"region","type":"text"},{"name":"r","field":"status","type":"text"}] and one metric widget → 200, active, no lint issue.
  2. Open /_console/apps/showcase_app/dashboard/qa_probe_dfn: one control ([data-testid=dashboard-filter-r], count 1).
  3. Type "north" and press Enter: the widget query carries {"status":{"$contains":"north"}}, the second definition's field, and the tile reads 0.

Expected: duplicate filter names refused at the save door. Actual: accepted; one control filters the other definition's field. Reproduced 2 times on fresh loads.

QA-source: #29002 · dashboards.global-filters-rescope · observed beside the clauses

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Claim: PM loop round 1 · 2026-11-02T07:20Z
    Session: session_01ADHkmPi25XXKx92Xmg4viS (seat); dev = card thread session_01ADHkmNmHGTQ6fDSRm1srQr
    Account: os-steve
    Branch: claude/project-thread-3p7kpx
    Worktree: the card thread's own checkout of objectstack-ai/objectstack
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/ui/dashboard.zod.ts (DashboardSchema.globalFilters, the duplicate-name refusal) and its tests; a new semantic entry under packages/spec/src/migrations/entries/semantic/ (never the generated registry.ts); the ledger rows and generated references the spec gates name; root .changeset/. Not ui/**, and not packages/lint unless a gate forces it (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); one CONTRACT_REVIEW_TIER review (a narrowing) before landing, effort: high
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: packages/spec dashboard.zod.ts DashboardSchema.globalFilters | the array takes any GlobalFilterSchema entries with no unique-name rule, while a widget binds a filter by name; #27445 refused an unknown binding but not a duplicate definition | dashboard authors who copy a filter and keep its name; a reader gets a silently wrong tile
    Thread-read: none
    Premise: holds (the card measured at framework 7fa9873f: PUT /api/v1/meta/dashboard with two globalFilters named r answers 200, and the board draws one control that filters the second definition's field; on origin/main 12aad143, globalFilters is z.array(GlobalFilterSchema).optional() with no uniqueness rule)
    Serial constraints cleared: none (the seat's other in-flight cards #28914, #28912, #28896 and #28993 hold disjoint files)

    Ruling-ref: the card ("duplicate filter names refused at the save door"); triage queued it in domain:spec at p2. It is the same class as dashboard-filter-binding-unknown (#27445).

    Amended: Metadata-protocol: semantic → narrowed. AGENTS.md (post-task checklist item 3): "a value refused or a requirement tightened is narrowed", and the #26725 sibling used narrowed (the dev's open question 1, report 6572629846). Clause-②: no (narrowing) stands.

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: dashboards.global-filters-rescope

    Triage: domain:spec · p2 · pm:queue · 2026-11-02T07:21Z

    ⛔ Not a claim.

    • Read fresh. The sibling refusal dashboard-filter-binding-unknown lives in packages/spec/src/ui/dashboard.zod.ts, so a duplicate globalFilters name refusal belongs beside it in the spec save door (platform first), with packages/lint following; domain:spec. A tile that silently shows a wrong number is a user-visible defect, p2, and adds no protocol key. It was seen beside the clauses rather than as the P1 item's FAIL, so the release rule does not apply; area:reports is the label dashboard cards already use.

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmNmHGTQ6fDSRm1srQr
    os-dev-report

    {
      "issue": 29023,
      "status": "done",
      "branch": "claude/project-thread-3p7kpx",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29162",
      "session": "session_01ADHkmNmHGTQ6fDSRm1srQr",
      "premise_still_valid": true,
      "summary": "DashboardSchema.globalFilters now refuses two or more entries under one filter key (name, else field; the globalFilterKey derivation, held equal by a test). One custom issue per repeated key at the second declaration's name (or field when that entry has no name), naming the key, every index and the remedy. Predicate in packages/spec/src/ui/duplicate-global-filter-names.ts (shape of data/duplicate-action-names.ts), attached to the array so DashboardSchema.shape.globalFilters carries it; nothing new is exported. Semantic entry ui-dashboard-global-filter-duplicate-name-refused (step 18, refused with a prescription, no conversion). Save door measured: the card body answers 422 INVALID_METADATA at globalFilters.1.name in default, publish and draft mode and stores nothing; a stored row that repeats a key still loads with _diagnostics valid:false carrying the refusal. Blast radius: 0 authored dashboards repeat a key (95 arrays, 14 multi-entry); one ui renderer unit test feeds resolveDashboardFilterDefs a duplicate directly, never parses through spec, stays green.",
      "tests": "head 8331c756, shared box. New spec test 11/11. pnpm --filter @objectstack/spec test: Test Files 791 passed (791), Tests 23368 passed, 1 todo, VERDICT command-exit 0. pnpm --filter @objectstack/lint test: 189/189 files, 7829 tests, exit 0. pnpm --filter @objectstack/metadata-protocol test: 297 passed, 4 skipped (301), 22062 tests passed, 26 skipped, exit 0. pnpm --filter @objectstack/spec typecheck exit 0 (test layer compiles, no new debt). Ablation: scripts/ablation-replace.mjs --delete of the .superRefine(refuseDuplicateGlobalFilterNames) line (anchor 1 to 0; spec tests import src, no build leg) turns 7/11 red (all refusal cases), 4 controls/predicate cases stay green; restored, blob == HEAD, git diff HEAD empty. First attempt with an empty --replacement was a refused no-op, not counted. Save-door probe: scratch saveMetaItem test over the verdict-test stub engine, deleted after the reading.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 8331c756 (7 paths vs merge base 12aad143): 106 commands, all exit 0 on final run; three first-pass exit 3 (prerequisite) re-run green: check-plugin-teardown-shape --self-test (fetched pinned control commit), check:skill-examples and check:dual-build-cjs-loads (built the named packages; organizations declarations mid-write on the first re-run, rebuilt). --ran: 106 derived, 106 run, 0 NOT-MEASURED. Offline check-changeset-no-major --event with the body's two lines, base 12aad143: exit 0, level axis accepts no (narrowing) + semantic. CI families: in_progress at report time, read the PR check-runs.",
      "dogfood_battery": "8331c756 · os-verify-lock VERDICT command-exit 0 (27m25s) · Test Files 385 passed, 3 skipped (388) · Tests 3187 passed, 28 skipped · no failures",
      "mcp_calls": "3 — mcp__github__issue_read get, mcp__github__issue_read get_comments, mcp__github__pull_request_read get (all reads)",
      "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#29162, run 41413520711, 8380 bytes sent = stored, assignees none); this os-dev-report comment via post-stamped.mjs",
      "files_changed": [
        "packages/spec/src/ui/duplicate-global-filter-names.ts (new)",
        "packages/spec/src/ui/dashboard.zod.ts",
        "packages/spec/src/ui/dashboard-global-filter-duplicate-name.test.ts (new)",
        "packages/spec/src/migrations/entries/semantic/18.ui-dashboard-global-filter-duplicate-name-refused.ts (new)",
        "packages/spec/dropped-refinements.baseline.json",
        "content/docs/references/ui/dashboard.mdx (regenerated)",
        ".changeset/29023-dashboard-global-filter-duplicate-name.md (new)"
      ],
      "line_budget": "340 additions, 4 deletions, 7 files (PR as listed by the platform)",
      "deviations": [
        "main not merged before pr_create: origin/main fea2dbb7 is 15 commits ahead, zero path overlap with this diff (8 spec files moved upstream, none of the 7 here), git merge-tree clean; merging would have forced a second full round incl. 27 min dogfood. Every reading above is on 8331c756 vs base 12aad143.",
        "No assignee or reviewer set on the PR: the standard order forbids MCP writes and PR assignees, which overrides the harness's best-effort assignee step.",
        "Not subscribed to PR activity: os-dev's dispatch contract overrides the platform subscription posture."
      ],
      "open_questions": [
        { "question": "Metadata-protocol line: the claim says semantic; AGENTS.md's taxonomy reads a refused value as narrowed, and the sibling object-duplicate-action-name changeset used narrowed. The level gate accepts both (both ship minor). Amend the claim?", "options": ["A keep semantic as claimed (copied verbatim, gate green)", "B seat amends the claim to narrowed and the PR body plus changeset follow"], "recommendation": "B, because AGENTS.md names 'a value refused or a requirement tightened' as narrowed and the closest precedent used it; it changes no level." },
        { "question": "Stored-row listing: os migrate meta --stored does not list stored dashboards this refuses. The action-name sibling added a detector to packages/spec/src/conversions/refused-shapes.ts (REFUSED_STORED_SHAPES) reading the same predicate.", "options": ["A follow-up card in domain:spec adding a dashboard detector reading duplicateGlobalFilterNames", "B fold it into this PR (widens the claim's file surface by one file plus its pinning test)", "C leave it: the stored row's _diagnostics already carry the refusal"], "recommendation": "A, because it keeps this PR on its claimed surface and the detector is a mechanical copy of objectDuplicateActionName." },
        { "question": "Door pin: the 422 at the dashboard save door is measured but not pinned in packages/metadata-protocol (outside the claim). Add a protocol test like protocol.object-duplicate-action-name-refusal.test.ts?", "options": ["A add it in a follow-up with the stored-row detector", "B no pin; the spec parse is the rule and the door's parse path is already pinned for objects"], "recommendation": "A, cheap and it guards the door's parse of dashboards specifically." }
      ],
      "out_of_scope_findings": [
        "class: c · reach: NOT MEASURED at a public door (schema parse accepts it; lint dashboardFilterDefs lets the later definition win over the built-in dateRange) · evidence: GlobalFilterSchema.name describes 'dateRange' as reserved for the built-in date range, but a globalFilters entry named dateRange on a dashboard that declares dateRange parses and silently replaces the built-in filter's binding · dedupe words: reserved dateRange global filter name, globalFilters dateRange collision, dashboard reserved filter name",
        "carrier: seat · noted, not filed — ui renderer test ui/packages/core/src/utils/__tests__/dashboard-filters.test.ts pins 'duplicate names win last' in resolveDashboardFilterDefs; now unreachable through the spec but harmless"
      ]
    }

    Stamped 2026-11-02T08:31Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmNmHGTQ6fDSRm1srQr
    os-dev-report

    {
      "issue": 29023,
      "status": "done",
      "round": "rework 1 (seat note: Metadata-protocol narrowed, merge main)",
      "branch": "claude/project-thread-3p7kpx",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29162",
      "head": "6172f51e5673a0b43faeb76af11d53317ab173e3",
      "session": "session_01ADHkmNmHGTQ6fDSRm1srQr",
      "premise_still_valid": true,
      "summary": "Changeset line now reads Metadata-protocol: narrowed (commit b0d38071); minor, the bang, the BREAKING banner and the adr-0087 registered marker unchanged. origin/main merged at 0d6d4061 through scripts/pm/os-regen-merge.sh (merge 6172f51e, no conflict; branch's regenerated dashboard.mdx kept, step 4 rebuilt). PR body NOT patched: the relay refused issue_patch with zero writes because the body's first non-empty line must be a seat signature, while the body opens with the required project attribution block and then Fixes. The stored body still reads Metadata-protocol: semantic.",
      "tests": "head 6172f51e, shared box. Closure build VERDICT 0. check:generated: all 14 artifacts up to date. spec test: 791/791 files, 23378 tests, 1 todo, exit 0. lint test: 189/189, 7830, exit 0. metadata-protocol test: 298 passed + 4 skipped (302), 22069 passed + 26 skipped, exit 0.",
      "gates": "dispatch-gates --commands on 6172f51e (7 paths vs merge base 0d6d4061): 106 commands, all exit 0 first pass; --ran: 106 derived, 106 run, 0 NOT-MEASURED. Offline check-changeset-no-major --event, base 0d6d4061: exit 0 with the narrowed line AND with the stored body's semantic line (level axis reads the PR body only; both ship minor). check-adr-0087-registration --base 0d6d4061: exit 0, reads the changeset as metadata-protocol-narrowed, registered.",
      "dogfood_battery": "6172f51e · os-verify-lock VERDICT command-exit 0 (31m21s) · Test Files 387 passed, 3 skipped (390) · Tests 3204 passed, 28 skipped · no failures",
      "mcp_calls": "1 this round — mcp__github__issue_read get_comments (read; claim amendment confirmed)",
      "api_writes": "1 this round — this report comment. issue_patch on PR 29162 refused by the relay before sending (signature rule), 0 writes.",
      "files_changed": [".changeset/29023-dashboard-global-filter-duplicate-name.md (one line)", "merge commit 6172f51e (main's files only)"],
      "line_budget": "diff vs main unchanged at 7 files; this round +1/-1 in the changeset",
      "deviations": [
        "PR body line not brought to narrowed: relay issue_patch refused (first non-empty line must be LANE#N · ACCOUNT · SESSION; the PR body's first two lines are the required attribution block, then Fixes). Not worked around. The seat records the current reading or patches the body itself.",
        "spec typecheck not re-run on 6172f51e: the merge moved no file of this diff and the closure build plus check:generated passed on the merged tree."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Stamped 2026-11-02T09:32Z.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-steve · session_01ADHkmPi25XXKx92Xmg4viS

    Landed · 2026-11-02T11:56Z

    Probe Reading
    PR #29162 MERGED 2026-11-02T11:50Z, merge commit 38a278b718, through the merge queue
    #29023 closed completed by Fixes #29023

    pm:dispatched is removed. Seat ACCEPT 6573856745 and a same-head CONTRACT_REVIEW_TIER PASS on PR #29162 (Clause-②: no (narrowing), Metadata-protocol: narrowed). Dashboard globalFilters entries that reuse a name are refused. Follow-ups: #29212 (stored detector and the metadata-protocol pin) and #29213 (the dateRange name collision) are now unblocked, and #29284 carries the ui DashboardComponentSchema mirror.

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

Metadata

Metadata

Assignees

Labels

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions