Skip to content

spec: a distinct() refinement on array schemas is dropped from the JSON Schema projection — emit uniqueItems so the taxonomy facets keep their no-duplicates rule #31036

Closed

Description

@objectstack-fleet

repo:cloud#3 · hotlong · session_01Q4EjjRhUZjdy8VXs6Kkur2

Filing gate: (c) should-fix carried from a contract review (#30980, review 6692597068 ④).

维护者速读

新的市场分类规定同一个维度里不能重复(例如职能不能写两次「销售」)。这条规则在 TypeScript 校验里生效,但导出成 JSON Schema 时丢了,用 JSON Schema 校验的工具(编辑器提示、外部校验)会放行重复值。需要把它导出成 JSON Schema 的 uniqueItems。

Readings

Expected

  • A uniqueItems: true arm in the refinement projection for distinct, and the 8 dropped-refinement rows retired.

Acceptance

  • The generated JSON Schema carries uniqueItems; the dropped-refinement ledger shrinks by 8.

Serial: after objectstack#30980 merges.

Suggested-grade: p2.

Activity

  1. added
    enhancementNew feature or request
    repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 kept · domain:spec · unblocked · 2026-11-06T19:54Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  4. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibzzqHZdAVqgBAvxXKB

    Claim: PM loop round 5 · 2026-11-06T20:11Z
    Session: session_01L9qibzzqHZdAVqgBAvxXKB
    Account: os-tesla (the card's assignee)
    Branch: claude/project-thread-5t399a (the card thread's designated branch; dev session session_01L9qibxH6kxj8gByoApv69p)
    Worktree: objectstack-issue-31036
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: packages/spec/src/shared/refinement-projection.ts (the distinct arm), packages/spec/dropped-refinements.baseline.json (rows retired), the regenerated JSON Schema artefacts, a projection test, one @objectstack/spec changeset. ⛔ the Zod schemas themselves (stop on breach)
    Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high (p2)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the spec's JSON Schema projection (objectstack, packages/spec) drops distinct(), so every JSON-Schema consumer (editor hints, external validators) admits duplicate taxonomy facets that the TypeScript parse refuses. The fix is the projection arm. It reaches every author of objectstack.manifest.json and every package request today.
    Thread-read: 6694081664
    Premise: holds. On origin/main 9d939c58 the baseline lists the facet rows (:1279-1308), and the projection has no uniqueItems arm.
    Serial constraints cleared: #30980 merged (468e0c16da, 2026-11-06T19:16Z)

    Not governed. It owes one contract review at CONTRACT_REVIEW_TIER (a narrowing of a published schema), run by the seat, then the ordinary queue landing.

    Order: /mnt/project-files/spec-seat-1/r2026-11-06/dispatch-31036.md (frame FRAME.md beside it).

  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibxH6kxj8gByoApv69p
    os-dev-report

    {
      "issue": 31036,
      "status": "done",
      "branch": "claude/project-thread-5t399a",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31047",
      "session": "session_01L9qibxH6kxj8gByoApv69p",
      "premise_still_valid": true,
      "summary": "Added a sixth arm, unique-items, to the closed refinement projection: src/shared/refinement-projection.ts declares UNIQUE_SCALAR_ITEMS (new Set(values).size === values.length over readonly JsonScalar[]) and scripts/lib/refinement-projection.ts emits it as uniqueItems: true beside the node's own keywords. The marketplace taxonomy's file-local distinct() helper now passes that declared predicate (same expression, type-only bound T extends z.ZodType of JsonScalar, per seat ruling A), so both facets and the 6 schemas that embed them publish the rule; 8 ledger entries (14 sites) retired and the measured block re-read (252 / 1023 / 728 / 0). Premise held on origin/main 9d939c58: distinct() was an inline arrow the projection could not see; the card's 8 rows were 8 entries holding 14 sites.",
      "tests": "head da4e41dd: spec build (real dts, os-verify-lock) exit 0, census 1023 dropped / 252 schemas / 728 projected incl. 14 unique-items; check:generated exit 0 (14/14 up to date); spec typecheck exit 0 (tsconfig.scripts.json compiles the new test incl. its ts-expect-error); pnpm --filter @objectstack/spec test VERDICT command-exit 0, Test Files 820 passed (820), Tests 25918 passed | 1 todo; targeted refinement-projection + dropped-refinements + src/marketplace: Test Files 9 passed, Tests 245 passed. Ablation (committed first, ablation-replace.mjs, anchor 1 -> 0 proven on disk): emit body replaced by void jsonSchema -> Tests 7 failed | 81 passed (88), incl. the 8-schema census case; restored, git diff HEAD empty, anchor count 1; suite imports src and scripts/lib directly so no dist leg.",
      "gates": "dispatch-gates --commands derived 82 families on da4e41dd; all 82 exit 0 (record /mnt/project-files/spec-seat-1/r2026-11-06/out/31036-gates-ran-da4e41dd.list); --ran: 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN. Four first runs exit 3 (prerequisite, not findings) re-run green: check-plugin-teardown-shape --self-test (git fetch --unshallow origin main), lint check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure (turbo build ./packages/** + dogfood^...). Local-only: check:skill-frame-freshness exit 0; check:dev-prereqs exit 1 NOT MEASURED, reason: 34 ui/ packages have no dist in this worktree (box state; diff touches no ui/). PR check-runs: not yet reported at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "line_budget": "296 additions / 52 deletions over 6 files (test +200, projection src +56/-1, generator +19, changeset +17, taxonomy.zod.ts +3/-2, baseline -49/+3); no skills/**, not Tier H, under 3,000 lines",
      "files_changed": [
        ".changeset/31036-unique-items-projection.md",
        "packages/spec/dropped-refinements.baseline.json",
        "packages/spec/scripts/lib/refinement-projection.ts",
        "packages/spec/scripts/refinement-projection.test.ts",
        "packages/spec/src/marketplace/taxonomy.zod.ts",
        "packages/spec/src/shared/refinement-projection.ts"
      ],
      "schemas_gaining_uniqueItems": [
        "marketplace/MarketplaceBusinessFunctions (root)",
        "marketplace/MarketplaceIndustries (root)",
        "marketplace/Package (businessFunctions, industries)",
        "marketplace/CreatePackageRequest (businessFunctions, industries)",
        "marketplace/UpdatePackageRequest (businessFunctions, industries)",
        "marketplace/MarketplaceListing (businessFunctions, industries)",
        "marketplace/TemplateManifest (businessFunctions, industries)",
        "marketplace/MarketplaceSearchResponse (items.element.businessFunctions, items.element.industries)"
      ],
      "deviations": "(1) DECLARED, seat ruling A (seat thread 2026-11-06 20:12Z swap, 20:14Z bound): taxonomy.zod.ts changes 3 lines inside distinct() — import, inline predicate swapped for the declared UNIQUE_SCALAR_ITEMS (same expression), type-only bound T extends z.ZodType of JsonScalar (Set equality matches JSON uniqueItems only on scalars). Schema shapes, messages, accept set unchanged; pinned by the Zod-unchanged test. (2) baseline measured.refinementSitesThatDidProject corrected 369 -> 728 (stale on main, 714 by arithmetic); the other three moved by this change. (3) No assignee/reviewer: pr_create carries none and the harness's MCP assignee/reviewer step was skipped, per the frame. (4) git checkout --detach on the shared /home/user/objectstack checkout (clean, it held the thread branch) so the worktree could take the branch; git fetch --unshallow origin main for the teardown self-test.",
      "mcp_calls": "3 — mcp__github__issue_read get, issue_read get_comments, list_pull_requests; 0 writes (plus REST reads via gh api for the Claim comment)",
      "api_writes": "2 — fleet-write relay POST /repos/objectstack-ai/objectstack/dispatches (pr_create #31047, read-back identical 11214 bytes), post-stamped issue comment on #31036 (this report)",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted in Acceptance notes, not filed: src/security/tenant-layer0-verdict.ts organizationIds uses the same Set-uniqueness refine on a scalar list and could compose UNIQUE_SCALAR_ITEMS (projection gap, no reach measured)"
      ]
    }
  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibxH6kxj8gByoApv69p
    os-dev-report

    Round 2 on PR #31047, head dd7ed18f, answering contract review 6695604767.

    {
      "issue": 31036,
      "status": "done",
      "branch": "claude/project-thread-5t399a",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31047",
      "session": "session_01L9qibxH6kxj8gByoApv69p",
      "premise_still_valid": true,
      "summary": "Added a sixth arm, unique-items, to the closed refinement projection: src/shared/refinement-projection.ts declares UNIQUE_SCALAR_ITEMS (new Set(values).size === values.length over readonly JsonScalar[]) and scripts/lib/refinement-projection.ts emits it as uniqueItems: true beside the node's own keywords. The marketplace taxonomy's file-local distinct() helper now passes that declared predicate (same expression, type-only bound T extends z.ZodType of JsonScalar, per seat ruling A), so both facets and the 6 schemas that embed them publish the rule; 8 ledger entries (14 sites) retired and the measured block re-read (252 / 1023 / 728 / 0). Premise held on origin/main 9d939c58: distinct() was an inline arrow the projection could not see; the card's 8 rows were 8 entries holding 14 sites. Round 2 (contract review 6695604767 FAIL): changeset now Clause-② no (narrowing) + BREAKING (published artifact narrows) banner + adr-0087 not-required (no-migration-prescription) marker, level still patch; PR body Clause-② no (narrowing) + Skills-docs: none line; refinement-projection.ts gains the ⛔ identity-parse paragraph on the unique-items arm and UNIQUE_SCALAR_ITEMS. Head dd7ed18f.",
      "tests": "head dd7ed18f: spec tsc exit 0; targeted refinement-projection + dropped-refinements + src/marketplace Test Files 9 passed, Tests 245 passed. Earlier, on da4e41dd (rework moved only the changeset and a docblock): spec build (real dts, os-verify-lock) exit 0, census 1023 dropped / 252 schemas / 728 projected incl. 14 unique-items; check:generated exit 0 (14/14 up to date); spec typecheck exit 0 (tsconfig.scripts.json compiles the new test incl. its ts-expect-error); pnpm --filter @objectstack/spec test VERDICT command-exit 0, Test Files 820 passed (820), Tests 25918 passed | 1 todo; targeted refinement-projection + dropped-refinements + src/marketplace: Test Files 9 passed, Tests 245 passed. Ablation (committed first, ablation-replace.mjs, anchor 1 -> 0 proven on disk): emit body replaced by void jsonSchema -> Tests 7 failed | 81 passed (88), incl. the 8-schema census case; restored, git diff HEAD empty, anchor count 1; suite imports src and scripts/lib directly so no dist leg.",
      "gates": "head dd7ed18f: check-adr-0087-registration exit 0 ([BREAKING+clause-②-narrowing] not-required read), check-changeset-no-major exit 0, check-empty-changeset exit 0, check:changeset-gate-self-tests exit 0, check:doc-authoring / spec-docblock-symbol-anchors / nul-bytes / issue-citations / closing-keyword-parity exit 0. PR check-runs on dd7ed18f: 55 success, 9 skipped, 0 failure. The da4e41dd reds (TypeScript Type Check, Test UI) were aggregators over lanes cancelled at 21:11Z by the dd7ed18f push; no lane concluded failure. Earlier full set on da4e41dd: dispatch-gates --commands derived 82 families on da4e41dd; all 82 exit 0 (record /mnt/project-files/spec-seat-1/r2026-11-06/out/31036-gates-ran-da4e41dd.list); --ran: 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN. Four first runs exit 3 (prerequisite, not findings) re-run green: check-plugin-teardown-shape --self-test (git fetch --unshallow origin main), lint check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure (turbo build ./packages/** + dogfood^...). Local-only: check:skill-frame-freshness exit 0; check:dev-prereqs exit 1 NOT MEASURED, reason: 34 ui/ packages have no dist in this worktree (box state; diff touches no ui/).",
      "dogfood_battery": "dd7ed18f · os-verify-lock VERDICT command-exit 0 (closure turbo build + pnpm --filter @objectstack/dogfood test, no shard, no path, 33m18s held) · Test Files 428 passed | 3 skipped (431) · Tests 3562 passed | 41 skipped (3603) · no failure",
      "line_budget": "321 additions / 52 deletions over 6 files, no skills/**, not Tier H, under 3,000 lines",
      "files_changed": [
        ".changeset/31036-unique-items-projection.md",
        "packages/spec/dropped-refinements.baseline.json",
        "packages/spec/scripts/lib/refinement-projection.ts",
        "packages/spec/scripts/refinement-projection.test.ts",
        "packages/spec/src/marketplace/taxonomy.zod.ts",
        "packages/spec/src/shared/refinement-projection.ts"
      ],
      "schemas_gaining_uniqueItems": [
        "marketplace/MarketplaceBusinessFunctions (root)",
        "marketplace/MarketplaceIndustries (root)",
        "marketplace/Package (businessFunctions, industries)",
        "marketplace/CreatePackageRequest (businessFunctions, industries)",
        "marketplace/UpdatePackageRequest (businessFunctions, industries)",
        "marketplace/MarketplaceListing (businessFunctions, industries)",
        "marketplace/TemplateManifest (businessFunctions, industries)",
        "marketplace/MarketplaceSearchResponse (items.element.businessFunctions, items.element.industries)"
      ],
      "deviations": "(1) DECLARED, seat ruling A (seat thread 2026-11-06 20:12Z swap, 20:14Z bound): taxonomy.zod.ts changes 3 lines inside distinct() — import, inline predicate swapped for the declared UNIQUE_SCALAR_ITEMS (same expression), type-only bound T extends z.ZodType of JsonScalar (Set equality matches JSON uniqueItems only on scalars). Schema shapes, messages, accept set unchanged; pinned by the Zod-unchanged test. (2) baseline measured.refinementSitesThatDidProject corrected 369 -> 728 (stale on main, 714 by arithmetic); the other three moved by this change. (3) No assignee/reviewer: pr_create carries none and the harness's MCP assignee/reviewer step was skipped, per the frame. (4) git checkout --detach on the shared /home/user/objectstack checkout (clean, it held the thread branch) so the worktree could take the branch; git fetch --unshallow origin main for the teardown self-test. (5) Review ④'s optional line inside distinct() not added: the seat's ruling capped taxonomy.zod.ts at three lines. (6) PR body rewritten once at the seat's request (issue_patch, trailer-signed), read-back identical 14007 bytes.",
      "mcp_calls": "6 — issue_read get, issue_read get_comments, list_pull_requests, pull_request_read get, get_job_logs x2; 0 writes",
      "api_writes": "4 — relay pr_create #31047; post-stamped report 6695323960; relay issue_patch PR #31047 body (round 2); post-stamped this round-2 report on #31036",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted in Acceptance notes, not filed: src/security/tenant-layer0-verdict.ts organizationIds uses the same Set-uniqueness refine on a scalar list and could compose UNIQUE_SCALAR_ITEMS (projection gap, no reach measured)"
      ]
    }
  7. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibzzqHZdAVqgBAvxXKB

    PR #31047 got the seat's ACCEPT at head dd7ed18f (comment 6696724228). The delta contract review 6696687698 is a PASS; the earlier FAIL 6695604767 is closed. The seat readied the PR and armed auto-merge via the relay. The card closes when the PR merges.

  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibzzqHZdAVqgBAvxXKB

    Landed · domain:spec#1 · 2026-11-06T22:15Z

    reading value
    PR #31047 merged 2026-11-06T22:14:49Z through the queue (seat-landed under the maintainer's 14:09Z word), merge commit 42eb126fb61e480a34fe896ccda1606ddb7a6ef2, final head dd7ed18f
    origin/main contains the merge commit (git merge-base --is-ancestor)

    State: card closed as completed by Fixes #31036; pm:dispatched removed from the card in this stroke. Carried out-of-scope note: tenant-layer0-verdict.ts:79 (organizationIds) has no uniqueItems carrier; the seat screens it for a card.

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

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3repo:cloudCard routed whole to the cloud PM: needs cloud code, a producer or a fact (cloud owns it to closure)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions