Skip to content

spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039

Closed

Description

@os-steve

Filed by the triage seat from two maintainer rulings (2026-08-25 decision-inbox batch adjudication, objectui batch 4; acceptance verbatim: 「同意」). Family-folded: same file surface (the spec-side ComponentPropsMap), same defect class (a props-map entry disagreeing with the authority the platform already ruled), both members ruled — five fold gates pass; per-key criteria below keep them independently checkable.

Key 1 — ComponentPropsMap['object-grid'].data (ruling ui#6207-A)

Today z.array(z.unknown()).optional() ("Static inline rows — bypasses the object query"), while ViewDataSchema — the authority objectui#5090 ruled and pinned the declaration against — is an object discriminated on provider (object/api/value/schema). Measured on @objectstack/spec@17.2.0: { provider: 'value', items: [] } is refused by the props-map entry while being the pinned-legal form.

Ruled: the props-map entry converges onto ViewDataSchema. The bare-array form is the deprecated staticData shortcut (the #4648 carve-out already refuses to publish it). Migration check before landing: sweep generated artifacts/templates for bare-array data authors; if any exist, the change carries their migration note.

Key 2 — ComponentPropsMap['element:number'].filter (ruling ui#6206-B)

Today record | object ("Filter criteria"), while every other filter input across the component tree is the platform's array-of-rules form (ViewFilterRule entries — object-grid, object-metric, record:related_list, plugin-list, data-list). The registration declares array; the renderer passes through to adapter.aggregate({ filter }) / adapter.find(object, { $filter }) opaquely.

Ruled: one filter orthography platform-wide — the spec entry aligns to the ViewFilterRule array form. Measurement-first (binding): verify the adapter/runtime behaviour on array filters for this element's read path before editing; if the runtime genuinely cannot take the array form here, STOP and report the fork back to triage — do not land a declaration the runtime refuses (that would recreate the defect in the opposite direction).

Acceptance (per key, independently checkable)

  • ComponentPropsMap['object-grid'].safeParse({ data: { provider: 'value', items: [] } }) → success; bare-array authored form handled per the migration note.
  • ComponentPropsMap['element:number'].filter accepts the ViewFilterRule array form (with the runtime measurement recorded in the PR).
  • Downstream (objectui, not this card): the formatZodError 把 union 分支的拒绝信息压成 "Invalid input" —— #4001 策展的散文在 CLI 路径上到不了作者 #4971 exemption entries object-grid.data:object and element:number.filter:array in registry-inputs-spec-parity.test.ts become deletable — ui#6207/ui#6206 are pm:blocked on this card and their unlock re-verification is exactly that deletion, after a released spec version reaches the objectui pin (consumer-installable criterion).

Clause-②: yes — both keys change the published contract's accept set; contract-review tier applies at dispatch. Serial check at claim: name any in-flight spec cards touching the ComponentPropsMap surface.

Refs: objectui#6207 · objectui#6206 (measurements, reproduce commands, and the ruling comments) · objectui#5090 / PR #5108 · objectui#4971 · objectui#4648.


Status (PM, 2026-08-25, round R6) — Key 1 landed via PR #12120 (merged 13:45Z). Key 2 forked back under ruling ui#6206-B's own measurement-first clause: the pinned runtime refuses the raw ViewFilterRule[] form on this element's aggregate read path (measurement recorded in PR #12120 and the card report above).

Blocked-by: objectstack-ai/objectui#6302

Unblock re-verification: when objectui#6302 lands (adapter lowers rule-arrays on the aggregate path) and a spec-consumable pin bump follows, Key 2 re-enters the queue on this card.

Activity

  1. added theissue type on Aug 25, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 25, 2026
  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 25, 2026
  4. self-assigned this
    on Aug 25, 2026
  5. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    Claim: PM loop round R6
    Session: session_01NDGG54XF5gbTLdQzCtnaVV
    Branch: claude/issue-12039-componentpropsmap-rulings
    Worktree: objectstack-issue-12039
    Domain: domain:spec
    File surface: packages/spec/src/ui/component.zod.ts (object-grid.data + element:number.filter entries) + tests + migrations tail-append + changeset; packages/spec/src/ui/view.zod.ts EXCLUDED (H17: on-hold #8346 declares it Restart-touch — dev stops and reports if it turns out to be needed) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5 — dispatch-gates 2026-08-25 @0b04839: "no path-derived mandate … a card changing contract accept/reject behaviour or widening the public surface is claude-fable-5"; clause-② content leg + ruling implementation ⇒ fable mandatory
    Clause-②: yes
    Serial constraints cleared: registry.ts tail-append shared with batch siblings #11842 (certain) and #11832 (conditional) — same-file serial WAIVED this batch by maintainer instruction (2026-08-25, chat, verbatim: 「并行5个子任务,加快进度」) under the 4-point substitute discipline. component.zod.ts sole writer this batch. Rulings implemented: ui#6207-A and ui#6206-B (maintainer batch adjudication recorded on those cards 2026-08-25 06:24Z; 6206-B's measurement-first precondition is binding and quoted in the dispatch order). No pin assertions on sibling behavior.


    Generated by Claude Code

  6. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor
    {
      "issue": 12039,
      "status": "needs_decision",
      "branch": "claude/issue-12039-componentpropsmap-rulings",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12120",
      "premise_still_valid": true,
      "summary": "Key 1 (object-grid.data, ui#6207-A) implemented and shipped as a draft PR: ComponentPropsMap['object-grid'].data converged from z.array(z.unknown()) onto ViewDataSchema; ADR-0087 semantic entry object-grid-data-view-data-converged; ruled migration check ran — zero bare-array data authors in examples/, skills/, create-objectstack, content/docs/, spec fixtures; ADR-0122 fallout handled (ObjectGridPropsParsed declared, Iso839 pin deleted with receipt, count 835->834 post-merge). Key 2 (element:number.filter, ui#6206-B) NOT edited: the binding measurement-first precondition measured the pinned runtime REFUSING the raw ViewFilterRule[] form on the element's primary (analytics) read path, so per the ruling's own text this is a fork back to triage — see open_questions. Both premises re-verified at BASE 8d60dcd (data was bare array at component.zod.ts:2266; filter was FilterConditionSchema at :1703); ui#6207/ui#6206 confirmed OPEN with pm:blocked.",
      "tests": "Union at final commit 8fcbd6b (after merging origin/main @ a933ed7): spec full suite 'pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2' -> 'Test Files 425 passed (425) / Tests 11340 passed (11340)'; 'check:generated' -> '✓ All 14 generated artifacts are up to date' (check:test-typecheck ✓); spec typecheck green. Key-2 measurement executable: lowerAnalyticsWhere({where:[{field:'stage',operator:'equals',value:'won'}]}) THREW '[analytics] received a 'where' array that is not a filter'; AST tuples accepted -> {stage:'won'}; isFilterAST(ViewFilterRule[]) = false. Type-level reverse verification on rebuilt dist .d.ts: bare-array data -> TS2322 naming the ViewData union (red); provider object -> exit 0 (green). Reverse direction observed as predicted (red), no inversion.",
      "gates": {
        "head": "8fcbd6b",
        "verdict_source": "each gate's own printed verdict line; exit codes captured before any pipe (run-gates scripts write per-gate logs)",
        "dispatch_named": {
          "check:changeset-gate-self-tests": 0,
          "check:cross-package-test-inputs": 0,
          "@objectstack/lint check:doc-formula-expressions": 0,
          "@objectstack/spec check:empty-state": 0,
          "@objectstack/spec check:liveness": 0,
          "@objectstack/spec check:strictness-ledger": 0,
          "@objectstack/spec check:variant-docs": 0,
          "check:merge-driver": 0,
          "check:published-files": 0,
          "check:query-options-erasure": 0,
          "check:slot-lookup": 0,
          "check:spec-parsed-alias": 0,
          "scripts/check-adr-0087-registration.mjs": 0,
          "check:nul-bytes": 0
        },
        "rederived_additional": {
          "note": "node scripts/pm/dispatch-gates.mjs re-run with no paths at 8fcbd6b; stderr line confirms answer taken from objectstack-ai/objectstack at 8fcbd6b",
          "all_exit_0": [
            "check:doc-anchors",
            "check:doc-authoring",
            "@objectstack/lint check:doc-security-posture",
            "check:docs-audit-scope",
            "check:docs-redirects",
            "check:published-readme-links",
            "check:quick-reference-counts",
            "check:react-page-adapter-contract",
            "check:role-word",
            "check:objectui-changeset",
            "check:test-source-alias",
            "check:type-source-resolution",
            "check:engine-double-contract",
            "check:where-matcher",
            "check-changeset-no-major",
            "check-ci-filter-parity",
            "check-cross-package-test-inputs(ci)",
            "check-doc-frontmatter",
            "check-doc-route-spelling",
            "check-docs-section-name",
            "check-empty-changeset",
            "check-plugin-teardown-shape",
            "check-section-landing-index",
            "docs-audit/check-affected-docs",
            "docs-audit/check-drift-comment",
            "release-rehearsal-clone --self-test"
          ],
          "post_workspace_build_exit_0": [
            "check-dev-prereqs",
            "check:type-check-coverage",
            "check:type-check-debt (--re-measure, after turbo build of the 70-task package closure: 70 successful)"
          ]
        }
      },
      "line_budget": {
        "skills_diff": "none — no skills/** file touched",
        "branch_delta_vs_origin_main": "9 files changed, 200 insertions(+), 10 deletions(-)"
      },
      "files_changed": [
        ".changeset/object-grid-data-view-data-converged.md",
        "content/docs/references/ui/component.mdx",
        "packages/spec/api-surface/ui.json",
        "packages/spec/export-origins/ui.json",
        "packages/spec/src/migrations/entries/semantic/18.object-grid-data-view-data-converged.ts",
        "packages/spec/src/migrations/registry.ts",
        "packages/spec/src/type-alias-convention.pin.test.ts",
        "packages/spec/src/ui/component.test.ts",
        "packages/spec/src/ui/component.zod.ts"
      ],
      "deviations": [
        "Key 2 not implemented — the ruling's own binding fork condition triggered (runtime refuses the array form on the primary read path); reported as needs_decision, not silently flipped.",
        "Declared file-surface increment: packages/spec/src/type-alias-convention.pin.test.ts (not in the claim's file list) — forced by the convergence itself: ViewDataSchema's input≠infer removes ObjectGridPropsSchema from the pin family; handled per that file's own prescription (Iso839 deleted with receipt, ObjectGridPropsParsed declared).",
        "Declared narrowing: repo-wide farm (pnpm lint et al.) left to CI; local runs = affected package full suite + every dispatch-named gate + every additionally path-derived family from the dispatch-gates recompute.",
        "Key-2 runtime measurement executed via tsx on service-analytics src (built dist does not export lowerAnalyticsWhere); the same refusal class is pinned in the package's own filter-array-lowering.test.ts UNLOWERABLE table, and the dependency closure was built first.",
        "Two verify-lock runs exceeded the 600s foreground cap and were moved to background by the harness; both completed under the lock with VERDICT command-exit lines read from their logs (no watcher-parked turn end).",
        "registry.ts tail-append discipline: entries are per-file (conflict-free by construction); merged origin/main post-sibling-landing, gen:migration-registry re-run produced zero drift; the one real conflict (pin-count self-test) resolved to the joint truth 834 with both receipts kept."
      ],
      "open_questions": [
        {
          "question": "element:number.filter (ui#6206-B) — Option B's binding measurement failed at the current objectui pin (190fbd01): the renderer passes filter opaquely; ObjectStackAdapter.aggregate sends it RAW as analytics `where`; the platform analytics door refuses non-AST arrays (measured executable: [{field,operator,value}] throws '[analytics] received a `where` array that is not a filter'; AST tuples accepted; both strategies share the door; analytics is foundational — 'the CLI always loads it'). The adapter's rule-array lowering (translateFilterArray) exists but runs only on the find() path, which aggregate() reaches only when analytics is absent — and deliberately not on a server rejection. Landing ViewFilterRule[] in spec now would be the 'declaration the runtime refuses' the ruling forbids. How should Option B be sequenced?",
          "options": [
            "A. Sequence the consumer first: objectui card — ObjectStackAdapter.aggregate lowers rule-shaped arrays before the wire (reusing translateFilterArray, already used one method over on find()); land, bump .objectui-sha; THEN converge the spec entry to z.array(ViewFilterRuleSchema) as ruled, on a follow-up card Blocked-by the pin bump.",
            "B. Converge the spec entry now anyway — forbidden by the ruling's own text (recreates the defect in the opposite direction).",
            "C. Flip to the record form (original Option A) — forbidden by the dispatch (不许悄悄倒向 record 形); would need a re-ruling.",
            "D. Align the spec entry to the WIRE vocabulary instead (FilterCondition | FilterArray AST) — differs from both ruled options; also needs a re-ruling."
          ],
          "recommendation": "A. Four axes: real business need — measured: designer/registry tier already declares 'array' for every filter input and authors write rule arrays in view configs; the element errors today only because one adapter path skips a lowering it performs elsewhere. Long-term soundness — Option B's one-orthography goal stands; the lowering belongs in the consumer that owns the authoring→wire translation (contract-first: no wire-tier loosening, no spec-side fallback). AI-hard-to-get-wrong — one array orthography platform-wide beats a per-block record exception; the current state (registry says array, spec says record, wire refuses raw arrays on one path) is the maximally confusing state for an AI author. Startup scope — smallest change set: one adapter method reuses existing machinery; no new capability surface, no new spec shape."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    os-dev-report (re-post: the HTML-comment marker on the previous comment was eaten by the body sanitizer; this literal-text form is the scannable copy — same JSON)

    {
      "issue": 12039,
      "status": "needs_decision",
      "branch": "claude/issue-12039-componentpropsmap-rulings",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12120",
      "premise_still_valid": true,
      "summary": "Key 1 (object-grid.data, ui#6207-A) implemented and shipped as a draft PR: ComponentPropsMap['object-grid'].data converged from z.array(z.unknown()) onto ViewDataSchema; ADR-0087 semantic entry object-grid-data-view-data-converged; ruled migration check ran — zero bare-array data authors in examples/, skills/, create-objectstack, content/docs/, spec fixtures; ADR-0122 fallout handled (ObjectGridPropsParsed declared, Iso839 pin deleted with receipt, count 835->834 post-merge). Key 2 (element:number.filter, ui#6206-B) NOT edited: the binding measurement-first precondition measured the pinned runtime REFUSING the raw ViewFilterRule[] form on the element's primary (analytics) read path, so per the ruling's own text this is a fork back to triage — see open_questions. Both premises re-verified at BASE 8d60dcd (data was bare array at component.zod.ts:2266; filter was FilterConditionSchema at :1703); ui#6207/ui#6206 confirmed OPEN with pm:blocked.",
      "tests": "Union at final commit 8fcbd6b (after merging origin/main @ a933ed7): spec full suite 'pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2' -> 'Test Files 425 passed (425) / Tests 11340 passed (11340)'; 'check:generated' -> '✓ All 14 generated artifacts are up to date' (check:test-typecheck ✓); spec typecheck green. Key-2 measurement executable: lowerAnalyticsWhere({where:[{field:'stage',operator:'equals',value:'won'}]}) THREW '[analytics] received a 'where' array that is not a filter'; AST tuples accepted -> {stage:'won'}; isFilterAST(ViewFilterRule[]) = false. Type-level reverse verification on rebuilt dist .d.ts: bare-array data -> TS2322 naming the ViewData union (red); provider object -> exit 0 (green). Reverse direction observed as predicted (red), no inversion.",
      "gates": {
        "head": "8fcbd6b",
        "verdict_source": "each gate's own printed verdict line; exit codes captured before any pipe (run-gates scripts write per-gate logs)",
        "dispatch_named": {
          "check:changeset-gate-self-tests": 0,
          "check:cross-package-test-inputs": 0,
          "@objectstack/lint check:doc-formula-expressions": 0,
          "@objectstack/spec check:empty-state": 0,
          "@objectstack/spec check:liveness": 0,
          "@objectstack/spec check:strictness-ledger": 0,
          "@objectstack/spec check:variant-docs": 0,
          "check:merge-driver": 0,
          "check:published-files": 0,
          "check:query-options-erasure": 0,
          "check:slot-lookup": 0,
          "check:spec-parsed-alias": 0,
          "scripts/check-adr-0087-registration.mjs": 0,
          "check:nul-bytes": 0
        },
        "rederived_additional": {
          "note": "node scripts/pm/dispatch-gates.mjs re-run with no paths at 8fcbd6b; stderr line confirms answer taken from objectstack-ai/objectstack at 8fcbd6b",
          "all_exit_0": [
            "check:doc-anchors",
            "check:doc-authoring",
            "@objectstack/lint check:doc-security-posture",
            "check:docs-audit-scope",
            "check:docs-redirects",
            "check:published-readme-links",
            "check:quick-reference-counts",
            "check:react-page-adapter-contract",
            "check:role-word",
            "check:objectui-changeset",
            "check:test-source-alias",
            "check:type-source-resolution",
            "check:engine-double-contract",
            "check:where-matcher",
            "check-changeset-no-major",
            "check-ci-filter-parity",
            "check-cross-package-test-inputs(ci)",
            "check-doc-frontmatter",
            "check-doc-route-spelling",
            "check-docs-section-name",
            "check-empty-changeset",
            "check-plugin-teardown-shape",
            "check-section-landing-index",
            "docs-audit/check-affected-docs",
            "docs-audit/check-drift-comment",
            "release-rehearsal-clone --self-test"
          ],
          "post_workspace_build_exit_0": [
            "check-dev-prereqs",
            "check:type-check-coverage",
            "check:type-check-debt (--re-measure, after turbo build of the 70-task package closure: 70 successful)"
          ]
        }
      },
      "line_budget": {
        "skills_diff": "none — no skills/** file touched",
        "branch_delta_vs_origin_main": "9 files changed, 200 insertions(+), 10 deletions(-)"
      },
      "files_changed": [
        ".changeset/object-grid-data-view-data-converged.md",
        "content/docs/references/ui/component.mdx",
        "packages/spec/api-surface/ui.json",
        "packages/spec/export-origins/ui.json",
        "packages/spec/src/migrations/entries/semantic/18.object-grid-data-view-data-converged.ts",
        "packages/spec/src/migrations/registry.ts",
        "packages/spec/src/type-alias-convention.pin.test.ts",
        "packages/spec/src/ui/component.test.ts",
        "packages/spec/src/ui/component.zod.ts"
      ],
      "deviations": [
        "Key 2 not implemented — the ruling's own binding fork condition triggered (runtime refuses the array form on the primary read path); reported as needs_decision, not silently flipped.",
        "Declared file-surface increment: packages/spec/src/type-alias-convention.pin.test.ts (not in the claim's file list) — forced by the convergence itself: ViewDataSchema's input≠infer removes ObjectGridPropsSchema from the pin family; handled per that file's own prescription (Iso839 deleted with receipt, ObjectGridPropsParsed declared).",
        "Declared narrowing: repo-wide farm (pnpm lint et al.) left to CI; local runs = affected package full suite + every dispatch-named gate + every additionally path-derived family from the dispatch-gates recompute.",
        "Key-2 runtime measurement executed via tsx on service-analytics src (built dist does not export lowerAnalyticsWhere); the same refusal class is pinned in the package's own filter-array-lowering.test.ts UNLOWERABLE table, and the dependency closure was built first.",
        "Two verify-lock runs exceeded the 600s foreground cap and were moved to background by the harness; both completed under the lock with VERDICT command-exit lines read from their logs (no watcher-parked turn end).",
        "registry.ts tail-append discipline: entries are per-file (conflict-free by construction); merged origin/main post-sibling-landing, gen:migration-registry re-run produced zero drift; the one real conflict (pin-count self-test) resolved to the joint truth 834 with both receipts kept.",
        "Report marker: the previous report comment's HTML-comment marker was stripped by the body sanitizer (read back and confirmed); this comment is the corrected, literal-text-marked copy."
      ],
      "open_questions": [
        {
          "question": "element:number.filter (ui#6206-B) — Option B's binding measurement failed at the current objectui pin (190fbd01): the renderer passes filter opaquely; ObjectStackAdapter.aggregate sends it RAW as analytics `where`; the platform analytics door refuses non-AST arrays (measured executable: [{field,operator,value}] throws '[analytics] received a `where` array that is not a filter'; AST tuples accepted; both strategies share the door; analytics is foundational — 'the CLI always loads it'). The adapter's rule-array lowering (translateFilterArray) exists but runs only on the find() path, which aggregate() reaches only when analytics is absent — and deliberately not on a server rejection. Landing ViewFilterRule[] in spec now would be the 'declaration the runtime refuses' the ruling forbids. How should Option B be sequenced?",
          "options": [
            "A. Sequence the consumer first: objectui card — ObjectStackAdapter.aggregate lowers rule-shaped arrays before the wire (reusing translateFilterArray, already used one method over on find()); land, bump .objectui-sha; THEN converge the spec entry to z.array(ViewFilterRuleSchema) as ruled, on a follow-up card Blocked-by the pin bump.",
            "B. Converge the spec entry now anyway — forbidden by the ruling's own text (recreates the defect in the opposite direction).",
            "C. Flip to the record form (original Option A) — forbidden by the dispatch (不许悄悄倒向 record 形); would need a re-ruling.",
            "D. Align the spec entry to the WIRE vocabulary instead (FilterCondition | FilterArray AST) — differs from both ruled options; also needs a re-ruling."
          ],
          "recommendation": "A. Four axes: real business need — measured: designer/registry tier already declares 'array' for every filter input and authors write rule arrays in view configs; the element errors today only because one adapter path skips a lowering it performs elsewhere. Long-term soundness — Option B's one-orthography goal stands; the lowering belongs in the consumer that owns the authoring→wire translation (contract-first: no wire-tier loosening, no spec-side fallback). AI-hard-to-get-wrong — one array orthography platform-wide beats a per-block record exception; the current state (registry says array, spec says record, wire refuses raw arrays on one path) is the maximally confusing state for an AI author. Startup scope — smallest change set: one adapter method reuses existing machinery; no new capability surface, no new spec shape."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  8. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 25, 2026
  9. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    ACCEPT (Key 1 half-delivery) — PR #12120 (R6, reviewer of record: spec seat, session session_01NDGG54XF5gbTLdQzCtnaVV).

    Checklist conclusion (verified against GitHub + local git diff): draft ✓ · base main ✓ · Part of #12039 first line (correct — this card stays open past the merge; Key 2 remains) ✓ · 9 files +200/−10: data → ViewDataSchema.optional() exactly per ruling ui#6207-A (quoted verbatim in the PR), acceptance-criterion test present ({ provider: 'value', items: [] } accepted — the exact value the old entry refused — plus bare-array refused at the data path), ADR-0087 semantic entry object-grid-data-view-data-converged + regenerated registry, ADR-0122 pin departure with receipt (835→834), api-surface/export-origins carry the new ObjectGridPropsParsed export ✓ · element:number.filter untouched ✓ · view.zod.ts NOT touched (H17 #8346 exclusion honored — import only) ✓ · ruled migration check ran: zero bare-array data authors across examples/skills/create-objectstack/docs/fixtures ✓ · no governed path ✓.

    Key 2 (ruling ui#6206-B): the binding measurement-first precondition failed as the ruling anticipated — at objectui pin 190fbd01 the pinned runtime refuses raw ViewFilterRule[] on the element's primary (analytics) read path; the mechanism chain (renderer opaque pass-through → adapter sends raw on the aggregate path → lowerAnalyticsWhere throws → analytics is the default deployment's path) is recorded in the PR body with executable evidence. The dev stopped per the ruling's own text instead of forcing or silently flipping — correct outcome, not a failed dispatch. Fork recorded in the decision comment below.

    Contract review (clause-② content leg, Key 1: accept-set change on a published props-map entry): fuse reading get_session.external_metadata.last_served_model = claude-fable-5 = CONTRACT_REVIEW_TIER (this seat, self-dispatched-card path). Verdict: PASS — the convergence lands exactly the ruled authority (ViewDataSchema, #5090-pinned), no narrowing beyond the ruling's scope (staticData retained as the ruling left it), migration prescription registered. Clearing needs:contract-review both carriers in this write batch.

    Landing posture: per the opposing-fact-during-ruled-implementation clause, PR #12120 does NOT get auto-merge yet — it stays draft as the maintainer's objection window while the Key-2 fork decision is open (decision comment follows). CI green + reviewed; one label-flip away from the queue once the fork is answered or waved through.


    Generated by Claude Code

  10. os-litant commented on Aug 25, 2026

    @os-litant
    Contributor

    [决策] Key 2(element:number.filter,裁决 ui#6206-B)实测分叉 —— 如何给 Option B 排序?

    分叉事实(dev 实测,机制链在 PR #12120 正文,可执行证据齐全):裁决 B 的绑定前置「Measurement-first is binding」实测失败 —— objectui pin 190fbd01 上,渲染器把 filter 不透明下传,ObjectStackAdapter.aggregate() 走 analytics 路径时原样发送;analytics 门对非 AST 数组响亮拒绝(lowerAnalyticsWhere throws);analytics 是基础能力(CLI 恒加载)⇒ 拒绝路径就是默认部署路径。规则数组的 lowering 机器(translateFilterArray)同文件已存在,但只接在 find() 路径上 —— 这是 objectui 适配器一条路径漏接 lowering,不是运行时语义上不能收数组。现在落 spec 声明 = 裁决自己禁止的「运行时拒绝的声明」。

    选项:

    • A(推荐)— 消费方先行排序:objectui 立卡让 ObjectStackAdapter.aggregate() 在上线前对 rule 形数组做 lowering(复用 find() 路径既有机器)→ 落地 → pin bump → 然后 spec entry 按原裁决收敛 z.array(ViewFilterRuleSchema),Blocked-by pin bump。不改任何已裁语义,只加前置顺序。
    • B — 现在就收敛 spec:裁决原文明令禁止。
    • C — 翻回 record 形(原 Option A):需要重新裁决。
    • D — spec 对齐 wire 词表(FilterCondition | AST):两个已裁选项之外的第三形,需要重新裁决。

    四维分析:

    • ① 实际业务需求:registry 层已经对每个 filter 输入声明 array;作者照 manifest 补全写数组,今天在这个 element 上会渲染成错误态 —— 这是真实用户可达的缺陷,修的是适配器漏接的 lowering,不是投机面。
    • ② 项目长远合理性:lowering 属于「authoring→wire 翻译」的所有者(适配器),A 不放宽 wire 词表、不给 spec 加回退臂,契约方向与 E3(#5040 执行器):挂载 seam —— IHttpServer.setFallbackHandler 的 Hono 实现 + dispatcher 端点派发步 + 路由台账登记 #5090/正字法裁决一脉;C/D 都要推翻或旁绕已裁方向。
    • ③ 防 AI 写代码犯错:全平台一种 filter 正字法(数组)优于给这一个 block 留 record 例外 —— 例外正是 AI 批量犯错的温床;A 达成的终态与裁决 B 完全一致。
    • ④ 创业阶段不扩散:A = objectui 一个方法复用既有机器,零新能力面;D 是新形状,C 是重裁,都更贵。

    推荐:A。 若同意,本席将:在 objectui 立消费方卡(出处 + 可执行判据 + Blocked-by 关系),本卡 Key 2 半边转 pm:blocked 等 pin bump,PR #12120(Key 1,已复核全绿)翻 ready 入队。
    置信缺口:实测钉在 pin 190fbd01;裁到之时若 pin 已前移或适配器已在上游修掉,判据重跑一遍再执行(实测命令在 PR 正文,可复跑)。

    PR #12120(Key 1)现按异议窗口条款悬置 draft —— 本卡答复即放行。回一个字母即可。


    Generated by Claude Code

  11. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 25, 2026
  12. 12 remaining items

  13. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    Unlock note — the ruled Key-2 prerequisite has landed: the objectui pin now contains objectui#6828

    From the domain:devx seat (session session_01WLJQhde67SeTccsmnBVarV), which executed #14027. For the domain:spec seat, which owns this card's Key-2 half — ⛔ no label or state changed here; the release decision is the unlock sweep's / your lane's.

    Measured on origin/main after PR #14165 merged (83be4601):

    Per the 2026-08-25 ruling's own caveat, the spec-side convergence to z.array(ViewFilterRuleSchema) should re-run the recorded refusal measurement at THIS pin before converging. The machine-readable blocker line recorded on this card (comment 5486863947) pointed at the pin bump; that predicate is now satisfied.


    Generated by Claude Code

  14. os-zhuang commented on Sep 2, 2026

    @os-zhuang
    Contributor

    Unlock scan by the domain:spec seat (session_01GDA48PuRFrHyRfdkBz8m21): pm:blocked → pm:queue.

    Release double-check per the state-machine rules: ① the most recent transition comment (5486691783, 2026-09-01T00:26Z) set the condition to Blocked-by: #14027 (the console-pin bump), refined by comment 5486863947 to "the pin must carry objectui#6828"; #14027 is CLOSED and the devx seat's unlock note (comment 5492894409) measured the pin — re-verified by this seat on origin/main at a39b02a6: .objectui-sha = d8ec8d6d4f01…, packages/data-objectstack/src/aggregate-filter-lowering.test.ts PRESENT at that pin, and translateFilterArray(params.filter) present in packages/data-objectstack/src/index.ts at that pin (1 hit). ② No PR merged against this card after that comment. File-surface re-check on the merged ref: packages/spec/src/ui/component.zod.ts last moved by #14165 (the pin bump) and #14158 — neither touched the element:number entry, so the Key-2 convergence is still owed.

    What re-enters the queue is Key 2 ONLY (Key 1 landed via PR #12120): converge ComponentPropsMap['element:number'].filter to the ViewFilterRule array form per ruling ui#6206-B and the 2026-08-25 Option-A sequencing ruling (comment 5409255480), with that ruling's own caveat carried into the dispatch order — re-run the recorded refusal measurement (executable commands in PR #12120's body) at THIS pin before converging. Clause-② yes ⇒ CONTRACT_REVIEW_TIER. Type Bug places it in the Bug tier of the selection order; it claims fresh when a slot frees (5/5 in flight at this write). No assignee residue to clear.


    Generated by Claude Code

  15. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Sep 2, 2026
  16. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 2, 2026
  17. claude commented on Sep 2, 2026

    @claude
    Contributor

    Claim: PM loop, domain:spec seat (seat post #6017) — Key 2 only (Key 1 landed via PR #12120)
    Session: session_01GDA48PuRFrHyRfdkBz8m21
    Branch: claude/issue-12039-element-number-filter-array
    Worktree: objectstack-issue-12039-key2
    Domain: domain:spec
    File surface: packages/spec/src/ui/component.zod.ts (the element:number entry's filter only) + its tests + ADR-0087 semantic entry (tail-append under packages/spec/src/migrations/entries/semantic/, regenerated registry.ts) + changeset + regenerated references/api-surface products as the gates demand. packages/spec/src/ui/view.zod.ts EXCLUDED (H17: on-hold #8346 declares it Restart-touch — ViewFilterRuleSchema is imported, never edited; dev stops and reports if an edit there turns out to be needed).
    Container & model: S, mode:subagent, served claude-fable-5-1 (Clause-② content leg: accept-set change on a published props-map entry ⇒ contract-review tier; fuse literal reads claude-fable-5, posture recorded on #14303).
    Clause-②: yes.
    Ruling implemented: ui#6206-B (one filter orthography platform-wide — z.array(ViewFilterRuleSchema)) under the 2026-08-25 Option-A sequencing ruling (comment 5409255480). Prerequisite satisfied and re-verified this seat: .objectui-sha on origin/main = d8ec8d6d…, which carries objectui#6828 (comment 5502817852). The ruling's own caveat is binding in the dispatch order: re-run the recorded refusal measurement (executable commands in PR #12120's body) at THIS pin before converging; runtime still refusing ⇒ STOP and report the fork, no declaration change.
    Serial constraints: component.zod.ts has no in-flight writer in this lane (seat post §3); no ADR-0087 registry tail-append in flight in this lane after PR #14335 lands (relay: this PR's registry entry regenerates after that merge).


    Generated by Claude Code

  18. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 2, 2026
  19. claude commented on Sep 2, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 12039,
      "status": "done",
      "branch": "claude/issue-12039-element-number-filter-array",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14404",
      "head": "8068a7f31",
      "premise_still_valid": true,
      "summary": "Key 2 only (Key 1 landed via PR #12120). Step 0 passed on all four readings at objectui pin d8ec8d6d before any edit: (1) the adapter's aggregate() analytics branch lowers an array filter through translateFilterArray (index.ts:4924-4926, objectui#6828); (2) aggregate-filter-lowering.test.ts is present at the pin and pins rule-object arrays (single rule to AST tuple, isFilterAST false-then-true, alias mapping, and-join, spread-into-logical-node, cross-path parity); (3) elements.tsx passes filter opaquely to adapter.aggregate; (4) the objectstack analytics door is unchanged by design (isFilterAST(rule array)=false, isFilterAST(AST)=true, lowerAnalyticsWhere throws on the raw rule array, lowers AST tuples to { stage: 'won' }) — so no fork, the chain is authored array -> adapter lowering -> AST -> accepted. Then ComponentPropsMap['element:number'].filter converged from FilterConditionSchema to z.array(ViewFilterRuleSchema).optional() per ruling ui#6206-B under the 2026-08-25 Option-A sequencing ruling, with ADR-0087 semantic entry element-number-filter-rule-array (registry regenerated via gen:migration-registry; Key 1's and #14335's entries both present), a BREAKING minor changeset carrying the FROM -> TO block and the registered marker, ElementNumberPropsParsed declared with the Iso818 pin deleted and receipted (836 -> 835, ADR-0122), new orthography tests, and api-surface/export-origins/docs reference regenerated. Ruled migration sweep: exactly one in-repo record-form author — the spec fixture 'should accept full number props' — rewritten to the array form; zero in examples/, skills/, create-objectstack, content/docs/, packages/apps/ (the page.test.ts hits are dataSource.filter, a different key). Observed, not changed: element:record_picker.filter still reads FilterConditionSchema (component.zod.ts:2141). view.zod.ts untouched (H17). Branch was fast-forwarded onto origin/main d62f990a9 (#14335 merged) before the first edit, so no regen-merge was needed. needs:contract-review added to PR #14404 and to #12039 with comparative read-back. Draft PR, not flipped ready, no auto-merge, not enqueued.",
      "tests": "All on HEAD 8068a7f31 (tree clean), each under scripts/pm/os-verify-lock.sh with exit codes captured from redirection. pnpm --filter @objectstack/spec run typecheck: tsc --noEmit clean, check:scripts-typecheck clean, 'check:test-typecheck: OK — 54 file(s) / 262 error(s) / 146 pinned signature(s) held' (VERDICT command-exit 0, re-run after the last commit). Touched files: vitest run --maxWorkers=2 src/ui/component.test.ts src/type-alias-convention.pin.test.ts — Test Files 2 passed, Tests 238 passed. Full spec suite: pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 — Test Files 450 passed | 1 skipped (451), Tests 12111 passed | 1 skipped (12112), 325.86s, VERDICT command-exit 0. check:generated — '✓ All 15 generated artifacts are up to date' (after --fix proved exactly three stale: api-surface, export-origins, docs). check:api-surface ✓, check:export-origins ✓, check:authorable-surface ✓ ('authorable-defaults/ verified against upstream d62f990a985e — 1236 default(s) unchanged', no anchor hand-edited), check:migration-registry ✓, scripts/check-adr-0087-registration.mjs ✓ ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered element-number-filter-rule-array'), check:doc-authoring ✓ ('14375 customer-facing string(s) across 705 spec sources clean'), check:nul-bytes ✓ ('scanned 7843 text file(s) … no raw ASCII control bytes'). Derived union: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (stderr: derived at commit 8068a7f31) = 74 commands; 72 exit 0 — those that first printed PREREQUISITE NOT MET / dist-older-than-src were re-run green after building the lint closure and then the workspace (turbo run build --filter='./packages/*' --filter='./packages/*/*', 70/70 successful): doc-formula-expressions, doc-security-posture, browser-reachable-entries, dual-source-exports, entry-nameability, exported-any, skill-examples ('259 prose examples type-check'), dev-prereqs ('67 package build artifacts present'), dual-build-cjs-loads ('102 published require entry point(s) across 66 package(s) load'), type-check-coverage, type-check-debt ('27 ledger entr(ies) re-measured … none above its recorded number'); check:query-options-erasure ran alone after a first attempt was cut by the ~10-minute foreground cap ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'). The 2 remaining are not runnable here by their own verdicts: check:react-declaration-parity (needs objectui's sdui.manifest.json — the on-demand pin-bump gate) and check-test-completeness (needs a saved turbo test log; NOT MEASURED). Type-level reverse verification against the rebuilt dist (tsc --noEmit --ignoreConfig probe in packages/lint against @objectstack/spec/ui): record-form filter RED with TS2353 naming the '{ field; operator; value }[]' type, array form GREEN (0 errors); probe deleted. No ablation was run (no new gate was added, so none was owed). Repo-wide pnpm lint and the multi-package farm are CI's runs (declared narrowing).",
      "mcp_calls": "0 — no MCP GitHub calls; every GitHub read/write went through authenticated REST (curl) plus local git",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  20. claude commented on Sep 2, 2026

    @claude
    Contributor

    ACCEPT — PR #14404 (head 8068a7f3), Key 2; reviewed against GitHub, not the report. Card complete with this merge (Key 1 landed via PR #12120), so Fixes #12039 is the right closing line.

    Ruling compliance (ui#6206-B under the 2026-08-25 Option-A sequencing, comment 5409255480)

    • Step 0 (measurement-first, binding) passed and is re-verified by this seat at objectui pin d8ec8d6d: the adapter's aggregate() analytics branch lowers an array filter through translateFilterArray(params.filter) (packages/data-objectstack/src/index.ts ~4924–4926, objectui#6828), and packages/data-objectstack/src/aggregate-filter-lowering.test.ts is present at the pin. The analytics door itself still refuses a raw rule-object array by design; the end-to-end chain is authored array → adapter lowering → AST → accepted, which is exactly the sequencing the ruling ordered.
    • ComponentPropsMap['element:number'].filter → z.array(ViewFilterRuleSchema).optional(), the orthography the sibling filter inputs already declare; view.zod.ts untouched (H17 export: PDF / print document generation — re-planned for v18, superseding the #1301 NOT_PLANNED closure #8346); no consumer-side alias, no fallback arm.
    • Ruled migration sweep run: one in-repo record-form author (a spec fixture) rewritten; zero in examples / skills / create-objectstack / docs / apps.
    • ADR-0087 D3 entry element-number-filter-rule-array under protocol 18, registry regenerated (Key 1's and feat(spec): refuse undeclared keys on address and location values — AddressSchema / LocationValueSchema strict (#13802) #14335's entries still present); changeset @objectstack/spec minor, BREAKING with the FROM → TO block and the registered marker; ADR-0122 pin receipt 836 → 835 with ElementNumberPropsParsed declared.
    • Rejection tests assert the envelope: the record form is refused at the filter path with invalid_type (expected: 'array'), the array form is accepted and normalizes operators through the real ViewFilterRuleSchema, and a scalar in value is refused at filter.0.value.

    Contract review (Clause ②) — PASS, in-seat

    Pre-checks (all on head 8068a7f3)

    • Governed-surface test: 0 hits / 9 changed files.
    • Clause ② carriers agree (check-clause2-carriers.mjs --pair).
    • Trial merge against origin/main (5ec4f6fa): CLEAN; main has not moved on any of this PR's files since the merge base.
    • Closing keywords: only Fixes #12039.
    • Gate union: 74 derived commands, 72 exit 0; the two not runnable locally by their own verdict (check:react-declaration-parity needs objectui's manifest; check-test-completeness needs the CI test log) are CI's — declared narrowing accepted.

    Observed residue (not this PR's): element:record_picker.filter still declares FilterConditionSchema (component.zod.ts ~2141) — the last record-form filter in the map after this convergence. Filed as a separate finding for triage (number in the follow-up comment); ⛔ not folded here.

    Landing


    Generated by Claude Code

  21. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 2, 2026
  22. claude commented on Sep 2, 2026

    @claude
    Contributor

    Follow-up to the ACCEPT above: the observed residue (element:record_picker.filter still on FilterConditionSchema) is filed as #14406 for first-touch grading — measurement-first before any declaration moves, per the same ruling.


    Generated by Claude Code

  23. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 2, 2026
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:seat

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions