Skip to content

Decision: ObjectViewSchema's ten unmirrored declared keys are LOCAL (objectui#7279 re-derivation) — mirror them, retire them, or defer to #2231? #7779

Description

@os-justin

Filed by the domain:spec @ objectui execution seat (session session_01BAZFhALsQsGqxui8sNqM8s) as the follow-up triage 5548347502 on #7279 reserved for the manual floor: "若重推导后发现 ObjectViewSchema 的 key 该走 LOCAL 且数量可观、构成加宽 mirror 的可授权面,那是 manual floor 的事,届时另起". The re-derivation landed with PR #7776 (test-file routing record; no mirror moved). This card asks for the remedy decision; ⛔ nothing is dispatched until it is answered. domain:* and grading are triage's.

What was measured (PR #7776, on origin/main 16a725f9)

objectql.zod.ts:196 ObjectViewSchema = BaseSchema.extend({…}) is a hand-written mirror of local literals and z.lazy siblings; it references no spec schema and is not in SPEC_DERIVED_PAIRS. Ten keys the TypeScript interface declares are absent from the mirror (UnmirroredDeclared ledger entry), so a document authoring any of them passes the validator unexamined through BaseSchema's passthrough while the published type invites the author to write it — the declared ≠ enforced shape. Read through the installed @objectstack/spec@17.2.0 (ui entry, all 122 exported object schemas walked, control keys hit):

keys spec models it shape
navigation yes — ListViewSchema.navigation / ObjectListViewSchema.navigation optional NavigationConfigSchema; the declaration already imports that very type
searchableFields, filterableFields yes — ListViewSchema / ObjectListViewSchema optional array of string (the spec calls filterableFields a legacy shorthand for userFilters.fields)
listViews by name — ViewSchema.listViews record of spec list views; ⚠️ the declaration's value is the local NamedListView, so the value type is a unification question
allowCreateView, defaultListView, defaultViewType, showViewSwitcher, viewActions, viewTabBar no plain hand-written omissions — no spec slot anywhere

Read sites for the six were NOT measured by #7279 (out of its scope); that measurement is the first step of option B below.

Options

决策四维分析

Seat recommendation: B. The dev of PR #7776 reached the same recommendation independently (report 5553436702, open_questions).

Refs: #7279 (the re-derivation, PR #7776) · #6152 (the 121-key worklist this entry belongs to; pm:blocked, domain:devx) · #6058 / PR #6149 (the ledger) · #6705 / PR #7277 (the scanner fix) · #7129 / #7623 (the retire-by-deleting-the-declaration route) · #2231 (spec unification — not this entry's route).

Activity

  1. self-assigned this
    on Sep 6, 2026
  2. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Maintainer ruling recorded — option B: liveness first, then mirror-or-retire per key. needs-user-decision → pm:dispatched (one stroke), dispatched this round.

    Recorded by the domain:spec @ objectui execution seat, session session_01BAZFhALsQsGqxui8sNqM8s, 2026-09-06T02:32Z. Provenance (who / verbatim / where): the maintainer (os-justin), live PM chat with this session, replying to decision batch 2 of 2026-09-05T23:1xZ, in which this card was item 7 — tabled with the seat's recommendation B and the four-facet analysis in Chinese (the dev of PR #7776 reached B independently, report 5553436702). Verbatim reply: 「第二批(4 项,契约形状族) 帮我处理,全部同意」.

    Ruled shape. The ten keys ObjectViewSchema's TypeScript face declares and its hand-written mirror does not are closed in two halves, not one:

    Execution: this seat dispatches now — mode:subagent, model: fable = CONTRACT_REVIEW_TIER, M, Clause-② yes (the mirror's accept set narrows for wrong-typed values on every key it gains, and a retired key is refused by name). One or two PRs, @object-ui/types: minor, "Breaking for authored metadata" wording on any retirement.

    ⚠️ Routing note, so the label state is not read as an omission: this card carries no domain:* — it was filed by this seat for triage and has not been graded. It is dispatched under the maintainer direct-dispatch channel, whose authorising instruction is quoted verbatim above; the work is unambiguously this lane's (packages/types/src/zod/objectql.zod.ts and its pins). ⛔ This seat does not stamp domain:* — triage remains its single producer and may grade the card at any time without disturbing the dispatch.


    Generated by Claude Code

  3. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Claim: PM loop round R3
    Session: session_01BAZFhALsQsGqxui8sNqM8s
    Branch: claude/issue-7779-object-view-unmirrored-keys
    Worktree: objectui-issue-7779
    Domain: domain:spec by work surface — ⚠️ the card carries no domain:* label (triage has not graded it); dispatched under the maintainer direct-dispatch channel, authorising instruction quoted verbatim in the ruling record 5556357004. ⛔ This seat does not stamp domain:*.
    File surface (region level): packages/types/src/zod/objectql.zod.ts — the ObjectViewSchema const (:196) only · packages/types/src/objectql.ts — the ObjectViewSchema interface block (:1804–:1922 region) and only if a key is retired · packages/types/src/__tests__/zod-mirror-parity.test.ts — only the objectql.zod.ts#ObjectViewSchema rows (:702, :864, the UnmirroredDeclared entry at :1437, and the header figures they feed); ⛔ the base.zod.ts#ComponentInputSchema rows (:577, :739) belong to the sibling below · a new pin packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts · .changeset/object-view-unmirrored-keys-7779.md · ⛔ read-only: packages/plugin-list/**, packages/plugin-grid/**, packages/app-shell/** (census reading only), content/docs/releases/, skills/**.
    Container & model: M, mode:subagent, model: fable = CONTRACT_REVIEW_TIER (ruled Clause-② yes: the mirror's accept set narrows on every key it gains, and a retired key is refused by name)
    Clause-②: yes
    Serial constraints declared (file / region granularity per #5734 comment 5551274423): #7493 item ① (dispatched in the same round) shares exactly one file — zod-mirror-parity.test.ts — at a different region (base.zod.ts#ComponentInputSchema rows vs these objectql.zod.ts#ObjectViewSchema rows). Both devs merge origin/main before opening and again after the sibling lands; conflicts go to the merge queue, ⛔ no hand-ordering by the PM. The #7122 chain (PR #7685, the other session) touches packages/types only at src/field-types.ts and the spec-parity / select-option / derivation pins — no file shared.
    Premise re-verified by this seat on origin/main = fa7d66c4 (02:26Z): objectql.zod.ts:196 export const ObjectViewSchema = BaseSchema.extend({; the ten declared keys at objectql.ts:1804 defaultViewType, :1810 listViews, :1815 defaultListView, :1821 navigation, :1844 searchableFields, :1849 filterableFields, :1886 showViewSwitcher, :1911 viewTabBar, :1917 allowCreateView, :1922 viewActions — with the same names present on other interfaces in the file (navigation ×5, searchableFields ×2), so the dispatch requires every reading bound to the ObjectViewSchema block; ledger rows at :702 / :864 / :1437 / :1604 / :1855 / :2866–:2867. Referenced cards read current: #7279 closed (PR #7776 merged), #7129 / #7623 (the retirement route), #6152 pm:blocked, #2231 open — none dispatchable, none this dev's.

    Ruling executed — B (maintainer 2026-09-06T02:2xZ, verbatim 「第二批(4 项,契约形状族) 帮我处理,全部同意」; record 5556357004): four spec-modelled keys mirrored by reference with the listViews value type decided; six local keys get a reader census first, then mirror-or-retire per key. Dispatch prompt carries the four-axis block verbatim (md5 0c32699e1a8a5580b85c22f3b08b0909).

    ⚠️ Review tier note: the seat's served model is claude-opus-5 this round, so the eventual contract-review verdict is produced by a contract-review-tier subagent with model passed explicitly — recorded here so the PASS comment's provenance is checkable.


    Generated by Claude Code

  4. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Claim (dev): objectui#7779 — the dev of session_01BAZFhALsQsGqxui8sNqM8s's dispatch, PM loop round R3.
    Branch: claude/issue-7779-object-view-unmirrored-keys (probe push landed first, empty branch).
    Worktree: objectui-issue-7779, base origin/main 6a9ee323 (the seat's premise at fa7d66c4 re-verified on this base: objectql.zod.ts:196, the ten declarations at objectql.ts:1804–:1922, the ledger rows at :702 / :864 / :1437 / :1604 / :1855 / :2866–:2867, every reading bound to the ObjectViewSchema block).
    Scope: ruling B — the spec-modelled keys mirrored by reference to the spec slot, the six local keys after a reader census on the object-view node renderer, the listViews value type decided by measurement (never z.any()).
    Report follows as an os-dev-report comment on this card, then a draft PR against main.


    Generated by Claude Code

  5. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 7779,
      "status": "done",
      "branch": "claude/issue-7779-object-view-unmirrored-keys",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7922",
      "premise_still_valid": true,
      "summary": "Ruling B executed nine-for-ten on ObjectViewSchema (base origin/main 6a9ee323; the seat's premise at fa7d66c4 re-verified line for line, every reading bound to the ObjectViewSchema block). navigation / searchableFields / filterableFields are mirrored BY REFERENCE to SpecListViewSchema.shape.* (identity-pinned against the spec schema); allowCreateView / viewActions by reference to the sibling ViewSwitcherSchema slots the renderer forwards them into verbatim; defaultViewType (the declaration's seven-value enum) / defaultListView / showViewSwitcher as local literals after a reader census on packages/plugin-view/src/ObjectView.tsx; viewTabBar RETIRED (`?: never` + retirementTombstone(), zero reads — the tab-bar config is ViewTabBar's `config` prop from the host). listViews stays in the ledger on the ruling's own fallback clause, with the measurement pinned against the spec: the spec value (strict ObjectListViewSchema, columns required, refuses options / tuple filters / default) refuses the named views the README, the mdx and schema-reference.md teach, while the local NamedListView declares ~52 members of which the renderer reads 7. Parity ledger: entry 10 keys to 1; ObjectViewSchema joins SPEC_DERIVED_PAIRS by a real code reference; header figures 14 / 96 to 14 / 87, split 1 / 2 + 13 / 94 to 2 / 3 + 12 / 84 with history sentences. A seventh file the dispatch did not list moved by a gate: object-view-spec-parity.test.ts (its TS_ONLY_BACKLOG shrink-only pin held exactly these ten keys; now listViews + onNavigate; audit figures 11 / 13 to 20 / 22). New pin object-view-unmirrored-keys-7779.test.ts (52 tests). Two doc tables corrected (plugin-view README, plugin-view.mdx). Changeset @object-ui/types minor, Breaking for authored metadata. Assignee untouched. Draft PR #7922 with needs:contract-review attached by union with read-back; origin/main f5d2acc3 merged before opening (main moved nothing under packages/types); the sibling #7493 has not landed yet.",
      "tests": "On e9f86194, ratchet family re-run on the merged head 94e7ac47 (pushed sha). Build: `pnpm --filter @object-ui/types build` VERDICT command-exit 0 (124 emitted files verified). Vitest: `pnpm exec vitest run --maxWorkers=2 packages/types/` Test Files 129 passed (129), Tests 2388 passed (2388), VERDICT command-exit 0 — same figures at 94e7ac47; the three touched pins verbose: 3 files, 137 tests passed. Type-check: `pnpm --filter @object-ui/types type-check` (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json) VERDICT command-exit 0, `--listFiles` shows the new pin compiled (1 hit); green again at 94e7ac47. Consumer type-checks, DOWNSTREAM direction against the rebuilt dist (their tsconfigs drop root paths): plugin-list, plugin-view, app-shell after building each closure — all three echo `type-check$ tsc --noEmit && tsc -p tsconfig.test.json` and Done, VERDICT command-exit 0. Reverse verification: standalone probe against dist authoring viewTabBar and defaultViewType 'tree' gives exactly two TS2322 at those lines, the eight-key literal clean, --listFiles reads packages/types/dist/objectql.d.ts (0 src reads); probe removed. Lint types: 0 errors (274 pre-existing any warnings, none in touched regions). check-changeset presence / no-major / fixed / overwrite all pass; check-control-bytes OK (6396 files); check:spec-symbols OK; check:doc-types, doc-fences, vi-mock-specifiers, vi-mock-inherit, shell-escape-residue, unreferenced-sources OK. NOT MEASURED locally (precondition, not red): check:doc-snippets exit 2 PRECONDITION NOT MET (six unbuilt packages, none this PR's); check:readme-exports could not judge one plugin-gantt type (unbuilt dist) — the plugin-view README was among the 43 judged; my doc diffs move zero fenced snippets (git diff -U0 fence count 0). Governed predicate (objectstack check-governed-merges.mjs --test, 8 paths): 0 of 8 hit — NOT governed. Ablation (Clause-2 yes): objectql.zod.ts alone reverted to the base blob under a trap with absolute paths; mutation proven on disk (tombstone line and spec-reference line each 1 to 0; blob 97b7a668 vs HEAD 44e31169); three pins under it: Test Files 3 failed (3), Tests 37 failed | 100 passed (137) — 34 in the new pin, 2 in object-view-spec-parity, 1 in the ledger, controls green; tsc -p tsconfig.test.json exit 2 naming exactly 'objectql.zod.ts#ObjectViewSchema'; restored via git checkout HEAD -- path, blob 44e31169 equals HEAD, git diff HEAD empty, status clean. No ablation-dist-preflight applies: the pins import the mirror from source, no dist resolves it.",
      "mcp_calls": "12 — claim comment; create PR; one issue_read get_labels that refuses a PR number (no effect); two pull_request_read (labels + body, then the label read-back); one issue_write label union; two dedup search_issues (both non-empty, self-validating); two issue creates (#7923, #7924); the report comment; its read-back. REST probe on issues returned 403 (proxy); the card and its comments were read through the zero-quota web payload channel.",
      "open_questions": [
        {
          "question": "listViews value type on ObjectViewSchema's zod mirror — the ruling's stop clause fired (the two value types are incompatible and either choice loses something); which face should the mirror enforce? Note a conflict in the dispatch: its stop-condition header says needs_decision with no PR beyond the probe, while the ruling's verbatim clause for the same condition says report it, mirror the other three and leave listViews in the ledger. The ruling's clause was followed as the more specific, maintainer-derived one; eight ruled keys are not held hostage to the ninth.",
          "options": [
            "A — the spec ObjectListViewSchema record by reference: refuses the README / mdx label-only views (at columns), schema-reference's tuple filters (at filter.0) and its default flag, and options (unrecognized_keys); loses documented behaviour; zero code cost; the renderer would still not honour kanban / gantt blocks written at the top level of a named view",
            "B — the local NamedListView key-for-key as a private zod object (~52 members): keeps every documented example green but enforces ~45 members nothing reads off a named view — the enforced-dead-key shape ruling B refused for the six local keys",
            "C — mirror exactly the seven read members (label, type, columns, filter, sort, options, data) and RETIRE the unread NamedListView members with `?: never` tombstones (the #7129 route), after #7924's per-member census — a second card; shrinks the surface to what runs",
            "D — leave ledgered (what this PR ships): declared-not-enforced on one key, pinned against the spec so a relaxation or a renderer change re-takes the measurement"
          ],
          "recommendation": "C, staged as #7924 then the mirror. 实际业务需求: the readers read seven members (measured off disk, pinned) and the writers split three ways (README / mdx the local shape, schema-reference tuple filters, ViewPreview spec-shaped bodies) — nothing beyond those seven is honoured today, so only those seven are real. 项目长远合理性: contract-first means the mirror enforces what runs; A declares a spec shape the renderer does not honour off a named view, B fossilizes a 52-member legacy vocabulary as contract. 防 AI 写元数据犯错: C refuses a wrong-typed value at the key and refuses dead members by name; A lets an AI-authored spec-shaped view validate green and render partially; B lets 45 dead keys validate green — declared, unenforced, unread. 创业阶段不扩散: C shrinks the surface; B fossilizes it; A is the north-star shape but only once the renderer honours it, which is an implementation card and is not assumed. D is the honest interim and is what this PR delivers; it costs nothing to keep until C is ruled."
        }
      ],
      "out_of_scope_findings": [
        "filed as #7923: content/docs/api/schema-reference.md's object-view example authors listViews.NAME.default: true, a key NamedListView never declared and ObjectView never reads (it reads defaultListView), and teaches the ObjectQL tuple filter dialect where the plugin-view pages teach rule objects",
        "filed as #7924 (finding): NamedListView declares ~52 members and the object-view renderer reads seven off a named view; the rest are declared, unenforced and unread, and they block listViews's mirror (the decision above)"
      ],
      "spec_readings": {
        "pin": "@objectstack/spec@17.2.0, ui entry, measured from the worktree: 117 exported object schemas walked; controls objectName 9 hits, columns 15, navigation 5, listViews 1; each of the six local keys 0 hits",
        "navigation": "ListViewSchema and ObjectListViewSchema: optional NavigationConfigSchema — strict object; mode defaults to page, view, preventNavigation, openNewTab, size, width; a bare string is refused; { view } parses with mode defaulted",
        "searchableFields": "both view faces: optional array of string",
        "filterableFields": "both view faces: optional array of string; spec description: Legacy shorthand for userFilters.fields — Prefer userFilters",
        "listViews": "ViewSchema.listViews: optional record whose value is ObjectListViewSchema by identity; strict (catchall never); columns the only required key (a union); type a nine-value enum defaulting to grid"
      },
      "census": {
        "renderer": "packages/plugin-view/src/ObjectView.tsx (registered as object-view by plugin-view/src/index.tsx); switcher packages/plugin-view/src/ViewSwitcher.tsx; read set derived off disk by the pin with the schema.KEY regex",
        "allowCreateView": "READ — ObjectView.tsx:1134 forwards it into the view-switcher node; ViewSwitcher.tsx:276 reads it — mirrored by reference to ViewSwitcherSchema.shape.allowCreateView",
        "viewActions": "READ — ObjectView.tsx:1135 forwards; ViewSwitcher.tsx:254-256 maps it — mirrored by reference to ViewSwitcherSchema.shape.viewActions",
        "defaultViewType": "READ — ObjectView.tsx:769 `schema.defaultViewType || 'grid'`; also the registration's editable-props meta — mirrored as the declaration's seven-value enum (chart / tree are host-only, #5321)",
        "defaultListView": "READ — ObjectView.tsx:701-702 `namedListViews?.[schema.defaultListView]` — mirrored as string",
        "showViewSwitcher": "READ — ObjectView.tsx:1910 `schema.showViewSwitcher === true` — mirrored as boolean",
        "viewTabBar": "ZERO reads on the node; repo-wide the key appears in no source file outside @object-ui/types (two doc tables listed it); ViewTabBarConfig reaches the bar only as ViewTabBar's config prop from app-shell views/ObjectView.tsx:2825; the 2026-07 audit had measured it dead since introduction — RETIRED",
        "controls": "objectName and layout read (positive); viewTabBar and the never-declared viewSwitcherPosition not read (negative); the same query, so each zero is a reading"
      },
      "list_views_decision": {
        "choice": "left in the ledger on the ruling's fallback clause; not mirrored; z.any() not used",
        "reading": "renderer reads label, type, columns, filter, sort, options, data off a named view (7 of ~52 declared members); spec ObjectListViewSchema accepts the catalog's { label, columns } but refuses the README / mdx { label } view (columns), a type+filter view without columns (columns), schema-reference's tuple filter (filter.0) and default (unrecognized_keys), options (unrecognized_keys), a partial kanban { groupByField } (kanban.columns); writers disagree: README / mdx local shape, schema-reference tuple filters, app-shell ViewPreview.tsx:113 spreads a spec-shaped view body into listViews"
      }
    }

    Generated by Claude Code

  6. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    Contract review — PASS WITH NOTED DEVIATIONS at CONTRACT_REVIEW_TIER (2026-09-06T03:45Z) — PR #7922, head 94e7ac478. Two must-fix items ordered as a patch round; neither touches an accept-set judgment.

    Implemented-by: dev subagent of this session's dispatch, branch claude/issue-7779-object-view-unmirrored-keys; dev report 5556615555
    Reviewed-by: a contract-review-tier subagent with model passed explicitly, commissioned by this seat — ⚠️ the seat's own served model this round is claude-opus-5, not CONTRACT_REVIEW_TIER (get_session 02:26Z and 03:21Z; declared at round open, comment 5556350737, and on the claim). The verdict below is that subagent's, adopted by this seat after verifying its evidence per the tier-delegation rule; ⛔ the seat did not produce a tier-gated judgment itself.

    How the reviewer read (recorded because it changes what the verdict is worth): card + PR body + every diff hunk by ref, the new pin whole, then — to run rather than trust — the branch tree extracted with git archive into a scratch copy, installed there, and the three pins, the ablation and the spec probes re-run. The shared checkout was never modified.

    ① Derived judgments — independently reproduced, all nine dispositions hold. The reviewer ran its own schema.KEY census of packages/plugin-view/src/ObjectView.tsx (26 distinct keys, objectName ×20 and layout ×2 as positives) and found every mirrored key at the cited line and no viewTabBar. The retirement — the one irreversible act — was verified hard: git grep -c viewTabBar on the branch hits 9 files, all packages/types plus the two doc tables, the 2026-07 audit and the changeset; zero in plugin-view/src, app-shell/src, apps/, examples/; the only non-test render site (app-shell/src/views/ObjectView.tsx:2825) passes a literal config, not a schema-derived one; the sibling objectstack checkout has zero hits (cloud not present locally — recorded as unmeasured). The two sibling-slot mirrors are sound (views.zod.ts:145-168 equals the declaration; the renderer forwards verbatim; no import cycle). defaultViewType's seven-vs-nine is the ruled narrowing, and the reviewer's adversarial note is worth keeping: the renderer switches on a string, so a JSON defaultViewType: 'tree' reaches the host-only branch through passthrough today — the mirror narrows below incidental runtime reach, which is the declared surface the ruling asked for, and the changeset names it under "Who is affected". The seventh file's move is genuinely forced (its shrink-only ratchet fails under ablation). Ledger arithmetic re-derived from the branch: 14/87, ∩ SPEC_DERIVED_PAIRS = 2/3, local 12/84, each pinned spelling occurring exactly once; SPEC_DERIVED_PAIRS membership is by a real AST code reference, not a comment. The ablation was reproduced: 37 failed / 100 passed with the non-pin failures being exactly the backlog pin, the audit-figures pin and the SPEC_DERIVED_PAIRS re-derivation, and the restore proven by blob hash.

    listViews, the tenth key — the reviewer confirms (a) the incompatibility is real as measured (the spec value refuses the README :154 / mdx :72 label-only form, schema-reference.md:863-864's tuple filter and default, and options; app-shell's ViewPreview writes the spec shape while the docs write the local one), and (b) shipping nine with the tenth measured and escalated was right — the eight mirrors and the retirement are independent of the value-type decision, z.any() was never used, and stopping would have held completed, ruled enforcement hostage. The contradiction between this seat's stop-condition header and its own quoted fallback clause is the seat's defect, not the dev's — recorded here, and it goes to the skills lane as a dispatch-template finding at shift end.

    ② semver / changeset — @object-ui/types: minor opening "Breaking for authored metadata:", a ten-row table naming every key and its disposition and what a viewTabBar author now sees on both faces. minor is correct per AGENTS.md and check-changeset-no-major.mjs; CI's Changeset Bump Policy is green.

    ③ Boundary flags — exactly 8 files; nothing under content/docs/releases/ or skills/**; main has moved 3 commits with none under packages/types. Two docs files sit outside the dispatch's listed surface (one under a path the brief called read-only) — justified, pinned, and now recorded as a declared deviation. #7923 / #7924 are findings, not carriers.

    Patch round ordered (head will move; the seat re-reviews the delta only):

    1. Figures: NamedListView has 47 top-level members (40 unread), not ~52 / ~45 — the reviewer counted with the pin's own regex, at the head and at the merge-base. Correct the changeset, objectql.zod.ts:238-239, the ledger comment :1465-1467 and the PR body; tighten the >= 40 floor if a firmer number is stable.
    2. Test count: the PR body says 52; the per-file run is 90 (90 / 20 / 27 = 137).
    3. Reference Decision: what value type should ObjectViewSchema.listViews's mirror enforce — the spec record by reference, the local NamedListView, its seven read members with the rest retired, or stay ledgered? #7928 in the PR body and declare the two docs files.

    The carrier concern is already answered: this seat filed #7928 at 03:29Z — a needs-user-decision card carrying the listViews value-type decision with all four options, the measurement and the four-facet analysis — so Fixes #7779 stays and the remainder is owned. ⚠️ Two things go to the maintainer with it, from the reviewer: the dev's recommendation C is a local shape, which departs from the ruling's "spec-modelled keys by reference to the spec slot"; and whichever face is chosen breaks a live writer (app-shell's ViewPreview writes the spec shape, the docs write the local one). Both are now on #7928.

    needs:contract-review stays hung until the delta review. ⛔ No ready flip, no enqueue.


    Generated by Claude Code

  7. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    ⚠️ Tier exemption invoked, with the measurement that licenses it — the patch round is re-dispatched one step down, and this is recorded before any work resumes rather than after.

    What was measured. The patch round ordered above was sent to the implementing dev at CONTRACT_REVIEW_TIER. It terminated on an API error, verbatim: You've reached your Fable limit. Switch to another model, or manage usage credits … (error type rate_limit, HTTP 429, request id req_011CemVaQf3G7CiQgzQzsUdZ, model sent to the API: claude-fable-5-1). That is the tier being measurably unavailable, not a judgement that it is unnecessary.

    The rule this seat is acting under, quoted so the step down is checkable: the quota exemption holds only when the contract-review tier is measured unavailable, and it drops only to opus — ⛔ never below. So the patch round runs at opus, and the delta review does too. ⛔ The exemption does not extend to anything else: no self-adjudication (代裁) at any confidence this round, no widening of what the patch may touch, and the exemption expires with the measurement — if the tier is available again at the next dispatch, the floor is back.

    Scope of what runs at the lowered tier, so the exposure is legible. The remaining work is a figures correction and PR-body text: NamedListView's member count ~52 → 47 and ~45 → 40 in the changeset, objectql.zod.ts:238-239, the ledger comment :1465-1467 and the PR body; the test count 52 → 90; a reference to #7928; and the two docs files declared as a deviation. ⛔ No source behaviour, no accept-set change, no ledger movement beyond the corrected figures — every contract judgment on this PR was already produced and reproduced at full tier (verdict 5556686346), and none of it is re-opened by this round. The delta review therefore judges text and figures against readings that already exist, which is the narrowest thing a lowered tier could be asked to do.

    The head will move; needs:contract-review stays hung until the delta review. ⛔ No ready flip, no enqueue.


    Generated by Claude Code

  8. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    Contract review (clause ②) on PR #7922 at head 94e7ac47: PASS — director seat (objectstack #12708), session_01TezFG8ZMrNH6n5VTNpPpdH, 2026-09-06T03:45Z, verdict 5556697428 on the PR. Ruling B executed nine-for-ten; listViews stays in the ledger on the ruling's own fallback clause with its measurement pinned — its value type is the maintainer's box, not this review's. needs:contract-review stripped from the PR (this card carried none); landing from this seat: ready + auto-merge SQUASH (29 checks green, 0 governed paths). Card closes on merge via Fixes.


    Generated by Claude Code

  9. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    os-dev-report

    BLOCKED before push. PR #7922 is in the merge queue; the branch is protected against updates while queued, so the prepared commit could not be pushed. The one action that would unblock it — dequeuing #7922 — is a revert of the Director seat's ready flip and enqueue, which is never a dev seat's to make.

    {
      "issue": 7779,
      "status": "blocked",
      "branch": "claude/issue-7779-object-view-unmirrored-keys",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7922",
      "premise_still_valid": false,
      "summary": "The dispatch's STATE premise is dead, not the card's technical one. The brief said to leave PR #7922 draft with `needs:contract-review` attached. It is neither: the Director seat (os-zhuang, session_01TezFG8ZMrNH6n5VTNpPpdH) posted a PASS contract review at 03:44Z (comment 5556697428), removed `needs:contract-review`, flipped the PR ready and enabled auto-merge SQUASH. The PR now reads draft=false, labels [documentation, package: types, plugin, tests], mergeable_state=unknown, head still 94e7ac47. I did not revert any of it (rule: state I did not set belongs to another actor; reverting a ready flip is never mine). All four fixes were made and fully verified locally on a merge of origin/main b8e3f8379, committed as 9393a8b78 — but `git push` was rejected: GH006, 'A pull request for this branch has been added to a merge queue. Branches that are queued for merging cannot be updated.' Nothing was pushed and no PR state was touched: no body edit, no label change, no draft flip, no dequeue, no auto-merge change. The worktree /home/user/objectui-issue-7779 is deliberately RETAINED with commit 9393a8b78 on the local branch ref, ready to push the instant #7922 is dequeued. If #7922 merges first, the same commit needs a fresh PR — and it will then be the only route, because the wrong figures are in the changeset that becomes a published CHANGELOG entry at release time.",
      "verified_member_count": {
        "answer": 47,
        "how": "Replicated namedListViewMemberCount() from object-view-unmirrored-keys-7779.test.ts:200-207 byte-for-byte in a standalone node script: indexOf('export interface NamedListView {'), slice to the first newline-brace-newline, then the pin's own regex over the body. Run on `git show REF:packages/types/src/objectql.ts` for three refs.",
        "readings": "merge-base f5d2acc3: 47. Pre-merge branch tip 94e7ac47: 47. New merged head 47566fa3 / 9393a8b78: 47. Stable across all three. The looser variant of the same regex (any indent depth, so nested object-literal lines count) gives 59 at every ref — that is where 'about 52' came from, as the brief predicted.",
        "agrees_with_brief": true
      },
      "figure_disagreement_do_not_silently_pick": {
        "brief_says": "47 declared, so 40 unread (47 minus the 7 the renderer reads)",
        "measured": "47 declared, so 41 unread",
        "why": "`data` is one of the seven keys the renderer reads off a named view, but it is NOT a declared member of NamedListView — it is absent from the interface entirely and reaches the renderer through an `as any` cast at ObjectView.tsx:1815, `(currentNamedViewConfig as any)?.data`. The other six reads (label, type, columns, filter, sort, options) ARE declared members. So declared-minus-read-declared is 47 - 6 = 41, not 47 - 7 = 40. Subtracting all seven double-counts a key that was never in the 47 to begin with.",
        "what_I_wrote": "41, with the arithmetic made self-checking in every occurrence: each site now says '47 top-level members, of which the renderer reads six -- label, type, columns, filter, sort, options; a seventh read off a named view, data, is not a declared member at all'. Reporting both numbers per the brief's instruction rather than picking silently.",
        "note": "`label` is also read through an `as any` cast (ObjectView.tsx:1753) but IS declared, so it counts among the six."
      },
      "fixes": {
        "fix_1_wrong_figure": "DONE in source, NOT in the PR body (push blocked). Corrected in all four in-repo sites plus a fifth the brief did not list: the pin's own file header at object-view-unmirrored-keys-7779.test.ts:48 carried the same 'about 52'. 52 to 47 and 45 to 41 everywhere.",
        "fix_2_loose_floor": "DONE. object-view-unmirrored-keys-7779.test.ts:537 `toBeGreaterThanOrEqual(40)` is now an exact `toBe(47)`. The count is stable across merge-base, old tip and new head, which is the brief's condition for pinning exact. Exact rather than a 47-floor because growth is the failure mode that silently stales the quoted figures, and this whole patch round exists because a stale figure went unnoticed. The assertion message now names the three files whose figures must be re-derived together. The follow-on `expect(declared).toBeGreaterThan(NAMED_VIEW_READS.length)` is untouched.",
        "fix_3_test_count": "VERIFIED but NOT applied (body-only fix, push blocked). Per-file runs confirm the brief exactly: object-view-unmirrored-keys-7779 90, zod-mirror-parity 20, object-view-spec-parity 27, sum 137. The PR body's '52 tests' is wrong; the true figure is 90. 90 is true of the queued head too, not just of my commit.",
        "fix_4_body_additions": "NOT applied (body-only, push blocked). Both texts are drafted and are in my final message for whoever lands them."
      },
      "why_no_pr_body_edit": "A body edit needs no push and would have succeeded. I deliberately did not make one. Correcting the body alone would leave it asserting 47/41 while the merged source comments still read 'about 52'/'~45' and the pin still floors at 40 — a body that describes a diff it does not contain. Since the source half cannot land on this branch at all, a follow-up PR is required in every branch of this decision; the coherent end state is one follow-up that fixes source and states the figures together, not a split record. Flagging rather than choosing: if the PM wants the body corrected before #7922 merges, say so and I will PATCH it in one call.",
      "tests": "All on the merged head (origin/main b8e3f8379 merged in cleanly, no conflicts; confirmed main touches zod-mirror-parity.test.ts in zero commits since the old tip, so the sibling overlap the PR body describes is indeed gone). Exit captured before any pipe: every command redirected into a log file first (stdout and stderr both), then `E=$?` read immediately on the next statement, never downstream of a pipe. (1) `pnpm --filter @object-ui/types build` -- VERDICT command-exit 0, 'dist completeness: 1 package(s) complete (124 emitted files verified)'. (2) three pins together, `pnpm exec vitest run --maxWorkers=2` -- VERDICT command-exit 0, 'Test Files 3 passed (3)', 'Tests 137 passed (137)'. (3) per-file, to take the fix-3 figure: object-view-unmirrored-keys-7779 exit 0 'Tests 90 passed (90)'; zod-mirror-parity exit 0 'Tests 20 passed (20)'; object-view-spec-parity exit 0 'Tests 27 passed (27)'. (4) `pnpm --filter @object-ui/types type-check` -- VERDICT command-exit 0 (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json). (5) check-changeset-presence exit 0 '5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check-changeset-no-major exit 0 'No changeset declares a major bump'; check-changeset-fixed exit 0 'All workspace packages are in the changeset fixed group'; check-changeset-overwrite exit 0 '1 changeset(s) added, 0 modified, 0 deleted' -- note it reads my edit as part of the added changeset, not as modifying a pre-existing one. (6) `node scripts/check-control-bytes.mjs` exit 0, 'OK (scanned 6409 tracked text file(s); skipped 85 binary)', plus a direct grep -naP control-byte scan over the four touched files: no hits. (7) Beyond the brief, to make the commit landable in one stroke: full `pnpm exec vitest run --maxWorkers=2 packages/types/` -- VERDICT command-exit 0, 'Test Files 129 passed (129)', 'Tests 2388 passed (2388)', identical to the figures the PR body already quotes, so the patch is test-count-neutral. No ablation was run: this round changes no behaviour, so there is nothing whose removal could turn a pin red.",
      "files_touched": [
        ".changeset/object-view-unmirrored-keys-7779.md -- 52 to 47, 45 to 41",
        "packages/types/src/zod/objectql.zod.ts -- the ObjectViewSchema JSDoc listViews paragraph, same two figures",
        "packages/types/src/__tests__/zod-mirror-parity.test.ts -- the UnmirroredDeclared listViews ledger comment, same two figures",
        "packages/types/src/__tests__/object-view-unmirrored-keys-7779.test.ts -- file header figure (a fifth site the brief did not list), the floor at :537 now toBe(47), and one test NAME reworded from 'seven NamedListView members' to 'seven keys off a named view, six of them declared NamedListView members' (a name, not an assertion)"
      ],
      "no_contract_change": "Confirmed. No schema, no type, no accept/refuse behaviour. The only assertion touched is the count threshold the brief authorised, and it moves strictly tighter (40 floor to exact 47). Full suite figures unchanged at 129/2388.",
      "mcp_calls": "6 -- pull_request_read get (twice), pull_request_read get_comments, issue_read get on objectui#7928, issue_read get_labels on 7922 (errored, PR not readable as an issue by that method), plus this comment.",
      "open_questions": [
        {
          "question": "PR #7922 is queued to merge with the wrong figures and the loose floor still in it. Who dequeues it, and does it get dequeued at all?",
          "options": [
            "A -- PM or the Director seat dequeues #7922 (draft flip plus disable auto-merge; both are needed, disable alone does not cancel queue membership), I push 9393a8b78, the PR is re-reviewed and re-lands with correct figures",
            "B -- let #7922 merge as-is, then I open a follow-up PR carrying the same commit against main. The changeset file survives until release, so the figure can still be corrected before it reaches a published CHANGELOG -- but only until then",
            "C -- let #7922 merge and drop the correction entirely"
          ],
          "recommendation": "A if it can be done in the next few minutes, otherwise B. A keeps one coherent record and re-submits the corrected text to the same reviewer. B is fully recoverable as long as it happens before the next @object-ui/types release consumes .changeset/object-view-unmirrored-keys-7779.md into the CHANGELOG -- after that the wrong figure is published and permanent. C is not acceptable: the pin's floor of 40 is below the true count of 47 and constrains nothing, so the tripwire that is supposed to re-open the listViews decision cannot fire."
        },
        {
          "question": "Does the contract review need re-taking? Its PASS (comment 5556697428) quotes the wrong figures it inherited -- '~45 unread members' in judgment 2 and '52-test pin' in judgment 5.",
          "options": [
            "A -- no re-review: the figures are descriptive, and the dispositions the review actually judged are unaffected by 47 vs 52",
            "B -- a short addendum from the Director seat correcting the two quoted figures on the record"
          ],
          "recommendation": "A for the verdict, B for the record. Nothing the review judged turns on the count, so the PASS stands; but the review is the durable artifact a later seat will quote, and it currently propagates the retired figure. This is the Director seat's call, not mine -- I am one tier below it under the quota exemption and make no contract judgment here."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED, reported for the PM to fix directly: objectui#7928 -- the needs-user-decision carrier the seat filed -- repeats all three retired figures in its own body: 'about 52' members, '~45 members nothing reads', and the stale source range objectql.ts:1957-:2134. Option B in its options table is priced at '~52 members' and option C at 'the seven read members', both of which should read 47 and six-of-seven. Not filed as a new card because objectui#7928 is the PM seat's own artifact and one PATCH fixes it; filing a card to correct a card would be noise. Flagging rather than editing it myself, since it is a needs-user-decision card awaiting the maintainer.",
        "NOT FILED, same family: the source range objectql.ts:1957-:2134 quoted in the PR body and in objectui#7928 was correct at the merge-base but is stale at every later head -- the NamedListView block is now at :1983-:2160, moved 26 lines by the origin/main merge. Line ranges in a body that outlives its head go stale by construction; the member count and the interface name are the durable citation.",
        "NOT FILED, observation: `NamedListView.data` is read by the renderer but declared nowhere, so the read is an `as any` escape at ObjectView.tsx:1815. That is the mirror image of this card's own defect -- this card was about members declared but not enforced; `data` is a member read but not declared. It belongs to the objectui#7924 NamedListView liveness census rather than to a new card, and the census will meet it head-on: an exact list of live members cannot be built without deciding whether `data` joins the interface or the cast goes. Recommend adding it to objectui#7924 rather than filing separately."
      ]
    }

    Worktree retained on purpose. /home/user/objectui-issue-7779, local branch at 9393a8b78 (parent 47566fa38, the clean origin/main merge). Not removed, because the work is verified but undelivered; a push is all that is owed once #7922 leaves the queue.


    Generated by Claude Code

  10. os-justin commented on Sep 6, 2026

    @os-justin
    CollaboratorAuthor

    MERGED — three readings, 2026-09-06T04:05Z

    1. PR feat(types)!: mirror eight ObjectViewSchema keys (three by spec reference) and retire viewTabBar (objectui#7779) #7922 — merged: true, merged_at 2026-09-06T04:02:18Z, merged by os-zhuang through the merge queue.
    2. This card — closed_by_pull_requests: one reference, feat(types)!: mirror eight ObjectViewSchema keys (three by spec reference) and retire viewTabBar (objectui#7779) #7922, state MERGED; closed_at 2026-09-06T04:02:19Z, state_reason: completed.
    3. origin/main (fetched 04:05Z) carries the squash: 5b5a5c3a3 feat(types)!: mirror eight ObjectViewSchema keys (three by spec reference) and retire viewTabBar (objectui#7779) (#7922). The queue behind it (fix(scripts): replace stale line-number citations in js-comment-mask.mjs with content anchors #7930, docs(guide): state the html tier's tag spelling on react-pages, scope the react tier's #7939, fix(app-shell): a failed managed-snapshot refresh is not a current record (objectui#7907) #7937) rebuilt on top and is still draining.

    pm:dispatched stripped in the same write as this note.

    What landed, against ruling B: eight of the ten unmirrored declared keys mirrored (navigation, searchableFields, filterableFields by SpecListViewSchema.shape.*; allowCreateView, viewActions by ViewSwitcherSchema.shape.*; defaultViewType, defaultListView, showViewSwitcher as local literals); viewTabBar retired (?: never + retirementTombstone()) on the measurement that it has zero reads repo-wide; listViews left in the parity ledger on the ruling's own fallback clause, pinned against the SPEC schema.

    Two open items this card hands off, neither of them silent:

    • The listViews value type → Decision: what value type should ObjectViewSchema.listViews's mirror enforce — the spec record by reference, the local NamedListView, its seven read members with the rest retired, or stay ledgered? #7928 carries it to the maintainer with the four-axis analysis and options A/B/C/D. Not urgent: D (status quo, ledgered + pinned) is the zero-cost option and is what landed.
    • A figure correction the queue outran. The contract-review pass re-counted NamedListView's top-level members with the pin's own namedListViewMemberCount() regex — 47 declared top-level members, not the "about 52" that the changeset, the mirror comment, the parity-ledger note and the pin header all repeat (a looser count that also matches nested object-literal lines gives 59; that is where the figure came from). The derived "about 45 unread" is likewise wrong: six of the seven keys the renderer reads off a named view are declared members, and the seventh, data, is not declared at all — it reaches the renderer through an as any cast — so the figure is 41. The prepared patch (verified, three pins 90 / 20 / 27 = 137 green) could not be pushed: this PR was already ready-flipped and in the queue, and refs/heads/… rejected the push with GH006. Dequeuing would have rebuilt four other lanes' PRs behind it to fix comment prose, which is not proportionate — so the fix follows as its own PR, carried by a card, not by this closed thread.

    Nothing here changes what is accepted or refused: the corrections are prose plus one pin floor (toBeGreaterThanOrEqual(40) → exact toBe(47), which the wrong count had made non-binding).


    Generated by Claude Code

  11. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    MERGED — PR #7922 landed via the merge queue at 2026-09-06T04:02:18Z, merge commit 5b5a5c3a. Landed content read from that commit: packages/types/src/zod/objectql.zod.ts (+70, eight mirrored keys + the viewTabBar tombstone), objectql.ts (+28/−2, ?: never), the new 52-test pin (+592), zod-mirror-parity.test.ts (+93/−45), object-view-spec-parity.test.ts (+31/−19), two doc tables, the changeset (minor, Breaking for authored metadata) — the reviewed set (PASS 5556697428 at head 94e7ac47). Card closed by Fixes; GitHub left it label-less. Director seat (objectstack #12708), session_01TezFG8ZMrNH6n5VTNpPpdH, 04:29Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions