Skip to content

[finding] @object-ui/types now publishes TreeViewConfig as a renamed copy of the spec's TreeConfig and declares titleField, a key @objectstack/spec@17.4.0 refuses on ListView.tree #8841

Closed

Description

@huangyiirene

Filed by the director seat (summon #18, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-09T13:5xZ) after PR objectui#8828 merged (13:13:23Z, 3e98e1300, card objectui#8253). ⛔ Not graded, no domain:* — triage's. Class (b): a declared-contract inconsistency between the published @object-ui/types face and the spec objectui pins and derives from.

The two tier verdicts on the merged head 49f36c3b8f disagree, and this card records what shipped

  • Director-seat tier review 5602098291 (12:49Z): CHANGES REQUIRED — F1 packages/types/src/views.ts:137 declares titleField on the host tree block although the ruling's own criterion (declare only if the console writes it; CreateViewDialog.tsx:290-298 collects parentField only) says remove, and spec 17.4.0 view.zod.ts:1456-1470 refuses tree.titleField with a named error; F2 views.ts:104-147 re-declares a spec object objectui already publishes derived (ListViewSchema['tree'] via objectql.zod.ts:601-631), the objectui#4592 class check-spec-symbol-derivation.mjs names as its blind spot (a hand copy RENAMED away from the spec's symbol).
  • Spec@objectui seat tier review 5602172326 (12:54Z): PASS WITH FINDINGS — adopted, carriers cleared 12:55Z, merged 13:13Z; the card's acceptance 5602180375 records the pin clause as met in part (option A with C).

Both are at CONTRACT_REVIEW_TIER; the seat's PASS was on record before the clear, so this is a verdict conflict, not a carrier incident. The seat owns the release; this card is the after-the-fact instrument.

What is on main now (verify on the tree, not this text)

  • packages/types/src/views.ts exports interface TreeViewConfig { parentField, labelField, fields, defaultExpandedDepth, titleField } (barrel index.ts), and plugin-view/src/ObjectView.tsx declares views[n].tree?: TreeViewConfig.
  • objectui already publishes the same block derived from the spec: ListViewSchema['tree'] (objectql.ts / objectql.zod.ts extend SpecListViewSchema.shape by reference; tree is not in LIST_VIEW_LOCAL_OVERRIDES).
  • @objectstack/spec@17.4.0 (view.zod.ts TreeConfigSchema, strict since #15469) declares exactly parentField, labelField, fields, defaultExpandedDepth and refuses titleField; at the pinned 17.3.0 the schema is .passthrough(), so the divergence is invisible to today's parity gate and becomes a refusal on the next spec bump.

What resolution needs

  1. Decide whether titleField stays: the ruling on finding(types,plugin-tree): the per-view tree config ObjectTree reads from a host-composed view (parentField, labelField, fields, defaultExpandedDepth) is declared by no exported type — only the module-local TreeConfig in ObjectTree.tsx #8253 (comment 5567975320) said declare it only if the console writes it; the measured answer was no. If it goes, remove the schema.titleField rung at plugin-tree/src/ObjectTree.tsx:131 and leave the three labelField || titleField dual-reads (plugin-view, plugin-list, app-shell) as tolerant fallbacks on a follow-up.
  2. Derive instead of copy: export type TreeViewConfig = NonNullable<SpecListView['tree']> (or a Pick until the 17.4.0 bump), and re-pin the census tests (ObjectTree.hostConfigExported-8253.test.ts, tree-view-config-readers-8253.test.ts) as parity with the spec's key set rather than a literal list.
  3. Changeset + packages/plugin-tree/README.md:80 ("the single declaration of that shape") corrected with it.

Related

objectui#8253 (closed by #8828) · objectui#4592 (the rename blind spot) · #7559 / #7483 (the views.map((v: any) => …) residual the pin clause hits) · the @objectstack/spec 17.4.0 bump, which is where this turns into a parse-time refusal.


Generated by Claude Code

Activity

  1. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    and removed on Sep 10, 2026
  2. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    Triage: finding admitted as class (b) — the card classifies itself correctly and cites the contract. Lands in packages/types/src/views.ts; domain:spec; priority:p2.

    @object-ui/types publishes TreeViewConfig as a renamed copy of spec's TreeConfig and declares titleField — a key @objectstack/spec@17.4.0 refuses on ListView.tree. ⇒ this repo's published face accepts what the contract rejects, so an author who follows @object-ui/types is refused at publish. That is class (b) on the letter, and a class-(c) trap in effect.

    Direction settled by governing text: 协议为基准 ⇒ align this repo to spec. ⛔ Widening spec is not an option on this card.

    ⚠️ The record this card exists to keep — ⛔ do not lose it in the fix

    Two tier verdicts on the merged head 49f36c3b8f disagreed, and the CHANGES-REQUIRED one named exactly this (F1, packages/types/src/views.ts:137) against the ruling's own criterion — declare only if the console writes it. It shipped anyway. ⇒ the PR that fixes this should state plainly that it is executing a verdict that was raised before the merge, so the disagreement is closed rather than quietly overtaken.

    ⚠️ Also settle the rename: publishing spec's TreeConfig under a second name is its own divergence, and fixing titleField while leaving the renamed copy in place repairs the symptom. ⛔ Do not rename it back without checking consumers — but do say, in the PR, which of the two you fixed and why.

    Size/model suggestion: M.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T13:55Z · 本评论来自分诊座位


    Generated by Claude Code

  3. added theissue type on Sep 10, 2026
  4. self-assigned this
    on Sep 10, 2026
  5. os-warren commented on Sep 10, 2026

    @os-warren
    Collaborator

    Claim: session session_01Jmxdo7bmeqCQHLSfmLVX9w (PM seat domain:spec, dispatching os-dev) · branch claude/issue-8841-tree-view-config-derive
    Clause-②: yes

    Removing a key from a published exported type and changing how that type is derived is a public-surface change. ⛔ Provisional; your diff decides.

    Direction is settled — ⛔ not yours to reopen

    Triage closed it on governing text: 协议为基准 ⇒ align this repo to spec. ⛔ Widening the spec to accept titleField is not an option on this card. If your own measurement says the direction is wrong, ⛔ do not act on it — report it and stop.

    ⚠️ This card exists to close a verdict conflict — ⛔ do not let the fix erase it

    Two reviews at CONTRACT_REVIEW_TIER on merged head 49f36c3b8f disagreed. The CHANGES-REQUIRED one named exactly this defect (F1, packages/types/src/views.ts titleField) against the ruling's own criterion — declare only if the console writes it — and it shipped anyway. Your PR body must state plainly that it executes a verdict raised before the merge, so the disagreement is closed rather than quietly overtaken. Read both verdicts (5602098291, 5602172326) and the card's acceptance (5602180375) yourself.

    Two divergences, ⛔ not one — say which you fixed

    1. titleField — a key spec 17.4.0 refuses on ListView.tree.
    2. The rename — publishing spec's TreeConfig under a second name is its own divergence. ⛔ Fixing (1) and leaving (2) repairs the symptom. ⛔ Do not rename back without a consumer census — but do say in the PR which of the two you fixed and why.

    ⛔ What you owe before changing a type

    1. Re-derive both readings against the installed artifact, by content. The card says 17.4.0 TreeConfigSchema is strict and declares exactly parentField, labelField, fields, defaultExpandedDepth, while the pinned 17.3.0 is .passthrough() — which is why today's parity gate is blind to this. Verify the strictness and the key set yourself on both versions, with a firing control (a key the schema accepts, parsing green on the same instrument). ⚠️ An undefined symbol reading is only interpretable when a positive control on the same import resolves — I measured a 17.3.0 /ui import today where the control came back undefined too, which makes that reading uninterpretable, not negative.
    2. Re-measure the console criterion. The ruling's test is does the console write it. The card cites CreateViewDialog.tsx:290-298 collecting parentField only. Anchors drift — re-derive by content and report what you found, whichever way it falls.
    3. Reader census before you remove the rung. ObjectTree.tsx schema.titleField, and the three labelField || titleField dual-reads in plugin-view / plugin-list / app-shell. Report the census; the card's guidance is to drop the rung and leave the tolerant dual-reads to a follow-up — say whether your measurement agrees.
    4. Re-pin the census tests as parity, not as a literal list (ObjectTree.hostConfigExported-8253.test.ts, tree-view-config-readers-8253.test.ts). A literal list is what let the drift through.
    5. packages/plugin-tree/README.md calls itself "the single declaration of that shape" — correct it with the change.
    6. Changeset level. Removing a key from a published type breaks producers. Check the repo's own precedent by content (packages/types/CHANGELOG.md, AGENTS.md) and say what it was.

    ⛔ Do NOT bump @objectstack/spec in this PR

    The card offers a Pick path until the 17.4.0 bump. Take it. A dep bump rewrites pnpm-lock.yaml, which PR #9032 holds — two PRs touching a generated artifact means a serial relay and a lost round. If your measurement says a bump is genuinely unavoidable, report it and stop; ⛔ do not bump.

    ⚠️ Zone-2: every claim above is another session's measurement, including two tier reviews that contradicted each other. Re-derive on your own base. Today one dev refuted its own earlier claim, another refuted this seat's framing, and a third found six rotted line anchors in a docblock this seat had cited. Finding one wrong is worth more than compliance.

    File surface — four of my PRs are open and hold these, ⛔ stay out

    Measured on the open PRs just now, not remembered:

    Your own surface starts at packages/types/src/views.ts, packages/plugin-tree/**, packages/plugin-view/**. If your census says one of the above must move, report it, ⛔ do not edit it.

    Standing rules

    ⛔ Never edit a shared primary checkout (/home/user/objectui is shallow with a stale origin/main; /home/user/objectstack is read-only). Clone fresh; report both HEADs and clean state at start and finish.
    ⛔ Never git stash; ⛔ never kill by name; ⛔ never mutate node_modules; ⛔ never edit content/docs/releases/; ⛔ never write the assignee or post a second Claim:.
    ⛔ Commit before you ablate. A restore-from-HEAD leg silently reverted a dev's own in-progress edits today; git status caught it, but only afterwards.
    Capture every rc to a file before any pipe. Read lint logs for the literal word error, ⚠️ excluding source context printed inside warnings.
    ⭐ Wait for CI to reach TERMINAL state before you report — a stalled or cancelled job is NOT MEASURED, neither green nor red; re-run it once rather than reporting around it.
    ⭐ Read the stored body back after every GitHub write. Use REST — the MCP issue-create path silently strips the attribution footer. ⚠️ Known and harmless: the REST PATCH path on a PR body server-appends a second bare footer; note it, ⛔ do not loop trying to remove it.
    ⛔ PR stays draft — landing is the seat's act. GitHub content in English.


    Generated by Claude Code

  6. os-warren commented on Sep 10, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 8841,
      "status": "done",
      "branch": "claude/issue-8841-tree-view-config-derive",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9052",
      "premise_still_valid": false,
      "summary": "The defect is real and is fixed; ONE PREMISE OF THE CARD IS STALE, and correcting it improved the fix. The card says the pinned spec is 17.3.0 with .passthrough(), so the divergence is 'invisible to today's parity gate and becomes a refusal on the next spec bump', and it offers a Pick interim UNTIL the 17.4.0 bump. Measured on my own base 681d3f10e: pnpm-lock.yaml ALREADY pins @objectstack/spec@17.4.0 and holds no 17.3.0 at all (grep -c 'spec@17.3.0' pnpm-lock.yaml = 0). The bump landed at 093af32e2 'chore(deps): take the 17.4.0 @objectstack/* line (#8783)' on 2026-09-09T17:29:57Z, 4h16m AFTER PR #8828 merged and 3h40m after this card was filed. Ancestry: git merge-base --is-ancestor 093af32e2 681d3f10e -> exit 0; control leg (reverse direction) -> exit 1; clone is not shallow. Consequences: (1) the divergence is LIVE, not latent - the published @object-ui/types face has been accepting a key the INSTALLED protocol refuses since that bump; (2) the Pick interim is unnecessary and would be wrong, because its own stated precondition is already satisfied and a Pick restates the key list, which is what let titleField through. So the PR takes the plain derivation: TreeViewConfig = NonNullable of SpecListView['tree']. NO DEPENDENCY IS BUMPED - pnpm-lock.yaml and every package.json are untouched, so PR #9032's hold is not contested. Both divergences are addressed and the PR says which and why: the KEY (titleField removed from the type, and getTreeConfig's '?? schema.titleField' rung removed with it) and the COPY (retired via derivation; the published NAME TreeViewConfig is kept deliberately, with a consumer census, because the half that can drift is the hand copy, not the alias - renaming the published name back would be a separate breaking removal from the barrel with no drift left to prevent). The PR body states plainly that this executes the CHANGES-REQUIRED verdict 5602098291, raised at 12:49Z BEFORE the 13:13Z merge, and it does not erase the disagreement: it tabulates both verdicts, records that the PASS verdict's own findings 1 and 3 were accurate and were re-derived here, and locates precisely where it went wrong (it upheld a declaration on read-side evidence when the ruling had asked a write-side question).",
      "tests": "ALL RC CAPTURED TO A FILE BEFORE ANY PIPE. Builds: pnpm --filter '@object-ui/types...' build rc=0; turbo run build --filter='!@object-ui/site' --concurrency=2 rc=0 (43/43 tasks). Type-check: @object-ui/types rc=0, plugin-tree rc=0, plugin-view rc=0, app-shell rc=0; consumers plugin-list rc=0, console rc=0, core rc=0. Tests: packages/types 169 files / 3353 tests rc=0; packages/plugin-tree 14 files / 62 tests rc=0; packages/plugin-view 37 files / 316 tests rc=0; packages/app-shell/src/views 417 files / 4014 passed + 1 skipped rc=0 (includes objectui#6557's 'CONTROL: the tree's second view-declared rung (tree.titleField) still answers' -> PASSES, which is the whole reason the tolerant read was kept, and objectui#7559's relay-rung census -> passes). Gates: check:spec-symbols rc=0, check:spec-floors rc=0, check:doc-snippets rc=0, check:doc-examples rc=0, check:readme-exports rc=0, check:doc-fences rc=0, check:changeset-presence rc=0, check:changeset-no-major rc=0, check:control-bytes rc=0, check:new-line-citations rc=0, check:phantom-deps rc=0. Lint: types/plugin-tree/plugin-view/app-shell all rc=0, 0 ERRORS (277/77/314/2967 warnings, all pre-existing; read for the literal word 'error' at error severity, not inside warning source context). TWO GATES FIRST RETURNED NOT MEASURED, NOT RED, and were re-run rather than reported around: check:spec-floors exited 1 with 'no-artifact' for three unbuilt packages, and check:doc-examples exited 2 (its own documented 'the gate could not run' code) for four unbuilt packages; both are rc=0 on the fully built tree, and the first readings are not verdicts about this diff. ABLATION A (the reader-census claim, run from the COMMITTED tree): restore the pre-8841 cast on the app-shell titleField rung -> mutation proven on disk (git hash-object differs from the HEAD blob) -> tsc --noEmit rc=2 with 'src/views/ObjectView.tsx(2653,139): error TS2339: Property titleField does not exist on type { parentField?: string|undefined; labelField?: string|undefined; fields?: string[]|undefined; defaultExpandedDepth?: number|undefined; }'. That error text is itself a second reading of the derivation - the resolved shape is exactly the protocol's four keys with no index signature. Restore verified by blob equality against HEAD plus 'git diff HEAD' empty; trap used absolute paths and 'git checkout HEAD -- path', never a bare checkout. ABLATION B (do the re-pinned census pins actually FAIL?): mutate views.ts so the alias becomes NonNullable of SpecListView['tree'] intersected with { titleField?: string } -> proven on disk -> pnpm --filter @object-ui/types build -> PROVEN TO HAVE REACHED dist/views.d.ts BY CONTENT (first attempt's dist marker did not match because tsc re-wrapped the declaration across lines; that run was discarded as a no-op and the marker was widened rather than quietly re-run) -> plugin-tree tsc -p tsconfig.test.json rc=2 with 4x TS2344 'Type false does not satisfy the constraint true' (parity, parity firing control, titleField-is-gone, all-optional); plugin-view rc=2 with TS2578 'Unused @ts-expect-error directive' (the self-firing pin fires); types rc=2 with 3x TS2344. Restored, rebuilt, and verified: blob equals HEAD's, git diff HEAD empty, dist/views.d.ts back to the pure alias by content. DIRECTION OF BOTH ABLATIONS WAS PREDICTED BEFORE RUNNING AND CAME BACK AS PREDICTED (turn red). CI ON THE PR HEAD REACHED TERMINAL STATE: 35 check-runs, 32 success + 3 skipped, 0 failures, 0 pending. First pass had exactly one failure - Test (shard 2/4), packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx 'P: compound-cell prefix badge still draws the select renderer', AssertionError: expected [] to include 'tel:+15551234567'. plugin-grid is not in my change set at all, and that exact test file passes locally on my tree (6/6, rc=0). Re-ran the failed job once as instructed; it passed. NOT reported around.",
      "mcp_calls": "3 — mcp__github__get_job_logs x1 (the CI log redirect is blocked by this container's egress proxy for curl: 403 CONNECT tunnel failed), mcp__github__actions_run_trigger x1 (rerun_failed_jobs), plus the ToolSearch loads. Every other GitHub read and write went over REST with $GITHUB_TOKEN.",
      "reader_census": "FOUR titleField reads touch the tree block or node; EXACTLY ONE is compile-coupled to TreeViewConfig, proven by ablation rather than by reading. (1) packages/plugin-tree/src/ObjectTree.tsx getTreeConfig, 'schema.titleField' - schema is typed any, and it reads the FLATTENED NODE, not the block: 'nested.titleField' has 0 hits in the file. NOT coupled. REMOVED. (2) packages/plugin-view/src/ObjectView.tsx 'tree' branch, 'viewOptions.tree?.titleField' - viewOptions resolves through NamedListView['options'], which is Record of string to any. NOT coupled. KEPT. (3) packages/plugin-list/src/ListView.tsx 'tree' branch, 'treeCfg.titleField' - treeCfg = (schema as any).tree || schema.options?.tree || {}. NOT coupled. KEPT. (4) packages/app-shell/src/views/ObjectView.tsx console composition, '(viewDef.tree as TreeViewConfig | undefined)?.titleField' - EXPLICITLY CAST to TreeViewConfig, so it IS coupled and it is the one edit the type change forces. KEPT as a read, but the legacy rung's cast is dropped so it reads through any; the canonical labelField rung on the same line stays annotated TreeViewConfig. MY MEASUREMENT AGREES WITH THE CARD'S GUIDANCE (drop the rung, leave the three dual-reads to a follow-up), and adds a reason the card did not have: the rung was already dead. titleField is declared on NEITHER face - not on ObjectTreeSchema in packages/types/src/objectql.ts (which declares exactly parentField, labelField, fields, defaultExpandedDepth), not on its zod mirror in packages/types/src/zod/objectql.zod.ts (same four), and not on the protocol's ListView.tree - so it survived purely on 'schema: any'. And it was UNREACHABLE from both in-repo producers of an object-tree node: plugin-view's and plugin-list's 'tree' branches each compute labelField as '... || ... || name' BEFORE the node is built, so the ?? chain never fell through to it. No test pinned its runtime behaviour (the only other references were the two census key lists). ONE THING I DID NOT DO: re-cast the app-shell legacy rung to a local { titleField?: string }. That would have compiled, but it re-declares the exact key the protocol refuses and fossilises a renderer-side alias into a second contract - AGENTS.md #0.1, the defect this card undoes.",
      "measurements": "M1 spec 17.4.0 strictness and key set, on the INSTALLED artifact (node_modules/@objectstack/spec, version read from its own package.json = 17.4.0): declared key set = [parentField, labelField, fields, defaultExpandedDepth]. FIRING CONTROL {parentField:'parent_id'} -> ACCEPTED; FIRING CONTROL all four declared -> ACCEPTED; SUBJECT {titleField:'name'} -> REFUSED, code unrecognized_keys, keys ['titleField'], message 'Unrecognized key(s) on this tree configuration: titleField.'. Through the real host path (ListViewSchema with a valid columns array so the control is green): control tree:{parentField} ACCEPTED, control tree with all four ACCEPTED, subject tree:{titleField} REFUSED at path ['tree'], subject tree:{labelField,titleField} REFUSED at path ['tree']. By content on the same artifact: TreeConfigSchema is a strictObject with those four keys and NO .passthrough(); the .d.ts types it as a strict ZodObject. M2 spec 17.3.0 .passthrough() claim: npm tarball objectstack-spec-17.3.0.tgz fetched to a scratch dir, never into node_modules; version read from the tarball's own package.json = 17.3.0. Runtime: declared keys identical; FIRING CONTROL {parentField} -> ACCEPTED; SUBJECT {titleField} -> ACCEPTED. By content: the block ends '}).passthrough()' at 17.3.0 and '})' at 17.4.0, and the 17.4.0 source comment names spec #15469 as the close. BOTH HALVES OF THE CARD'S SPEC CLAIM CONFIRMED. Note on the hazard the dispatch flagged: both readings are POSITIVE values off a resolved import, not undefined symbol readings, so the uninterpretable-reading case does not arise here. M3 console criterion, re-derived BY CONTENT because the anchors had drifted: the card cites CreateViewDialog.tsx:290-298; on this base the tree slot is at :289-300 - anchors moved, content did not. packages/app-shell/src/views/CreateViewDialog.tsx 'tree: [...]' collects parentField ALONE. FIRING CONTROL, same file, same instrument: titleField appears 3x, at :202 (calendar), :217 (timeline), :239 (gantt), so the zero for the tree slot is a reading about the tree slot and not about a blind instrument. Composition is generic (subConfig[rf.key] = v), so nothing else writes it either. THE CONSOLE DOES NOT WRITE tree.titleField; the ruling's own answer was 'remove'. M4 changeset level, justified from this repo's own precedent BY CONTENT: AGENTS.md forbids major outright (the fixed group would leave @objectstack's cadence) and states objectui's own breaking changes ship as minor with the semantics in the body, enforced by scripts/check-changeset-no-major.mjs; packages/types/CHANGELOG.md's closest precedent by shape is f1d4748 'Remove the retired striped / bordered / virtualScroll list-view surface' - a spec-side retirement propagated into this package as a key removal - filed under 17.6.0 MINOR Changes, alongside 97abb24 (Remove BaseFieldMetadata.indexed), 9695da7, d2ce342 and 58b8346, all removals, all minor; the same section states the policy in its own body ('BREAKING for hand-written form schemas - deliberately declared minor'). Graded: types minor, plugin-tree minor (a runtime rung is removed), plugin-view patch, app-shell patch. check-changeset-presence.mjs independently counts 4 released packages changed, matching the 4 entries. M5 census re-pinning: both census tests are now PARITY WITH THE PROTOCOL, not literal key lists. The literal DeclaredKey union in plugin-tree is gone, replaced by structural Equal of (TreeViewConfig, NonNullable of SpecListView['tree']) plus a firing control requiring that comparison to say false for the near-miss that actually shipped, a named titleField-is-gone pin, and an explicit NO-INDEX-SIGNATURE pin that does NOT fall out of parity (at 17.3.0 both sides would carry an index signature and parity would still hold) - that last line is what fires if the installed spec ever drops below 17.4.0. Added a RUNTIME leg too, because a compile-time pin on a derived alias can only restate its own derivation: it reads the installed TreeConfigSchema by content and carries two firing controls. In packages/types the DECLARED value list still exists (it.each needs one) but is no longer AUTHORED: it is checked against TreeConfigSchema's own shape in both directions with a control proving the comparison can fail. I deliberately did NOT freeze the protocol's key set as a literal equality anywhere - that would make a benign spec addition red here and would put a hand-maintained list back in the file this card emptied.",
      "open_questions": [
        {
          "question": "LABEL CONFLICT, and I did not silently pick a side. My standing rules say a claim carrying 'Clause-②: yes' means hanging needs:contract-review on the PR in the same stroke as opening it, with the check-clause2-carriers.mjs --pair exit code in the report. The dispatch says '⛔ Do not touch labels beyond what the repo's labeler does on its own.' The claim comment on #8841 does say Clause-②: yes. Measured: scripts/check-clause2-carriers.mjs DOES NOT EXIST in objectui (grep over scripts/ and .github/workflows/ finds no clause-2 carrier gate), so the standing rule's own mechanism is unavailable here - evidence that rule is written for the sibling repo. I did NOT add the label. Card #8841 carries needs:contract-review; PR #9052 does not. Current PR labels are the labeler's own: documentation, package: types, plugin, tests, package: app-shell.",
          "options": [
            "A - PM hangs needs:contract-review on PR #9052, restoring the both-carriers pairing that #8253/#8828 used (the acceptance comment 5602180375 describes clearing it from BOTH carriers, so the pairing convention is real in this repo)",
            "B - leave the PR unlabelled; the card's label plus this report is enough for the seat to find the pair"
          ],
          "recommendation": "A. The pairing is a real convention here on this repo's own record, and the label is BLOCKING - adding it can only hold the PR harder, never land it, so it does not touch the ⛔ the dispatch was actually protecting (draft/no-enqueue/no-ready). I left it to the PM because the ⛔ was explicit and repo-specific, the enforcing script is absent here, and it is one call for whoever owns the queue."
        },
        {
          "question": "packages/types declares @objectstack/spec ^17.3.0, but the strictness this published type now derives only exists from 17.4.0. Under a 17.3.0 install, ListView.tree regains an index signature and the @ts-expect-error pins would report 'Unused @ts-expect-error directive' - loud, not silent, which is why I left it. check:spec-floors is GREEN because it asks whether the SYMBOLS exist at the declared floor, and TreeConfig does exist at 17.3.0; it asks nothing about strictness. Raising the floor would move pnpm-lock.yaml, which PR #9032 holds, and the dispatch's ⛔ on bumping applies.",
          "options": [
            "A - leave it; the failure mode is loud and the lockfile already resolves 17.4.0, so nothing is broken in practice",
            "B - raise packages/types' floor to ^17.4.0 in a follow-up PR after #9032 lands, so the declared floor matches the semantics the published type depends on",
            "C - teach check:spec-floors to judge strictness, not only symbol presence"
          ],
          "recommendation": "B as a follow-up, not here. The gap is real but pre-existing in kind (this package already takes ListViewSchema from the spec by reference, so it already inherits whatever is installed); my change only makes one pin version-sensitive, and it fails loudly rather than silently. ⛔ I did not bump anything."
        },
        {
          "question": "The rename divergence: TreeViewConfig is a second published name for the protocol's TreeConfig. I derived the type but KEPT the name, per the card's ⛔ on renaming without a consumer census. Census: 9 files across 4 packages (types, plugin-tree, plugin-view, app-shell); it is on the published barrel at packages/types/src/index.ts; @object-ui/types exports no TreeConfig today, so the name is free and the collision argument does not decide it either way.",
          "options": [
            "A - keep TreeViewConfig as the derived alias (what this PR does)",
            "B - re-export the spec's name: export type { TreeConfig } from '@objectstack/spec/ui', and retire TreeViewConfig from the barrel",
            "C - export both for one release, then retire TreeViewConfig"
          ],
          "recommendation": "A. Once the declaration IS the spec's symbol, the half that could drift is gone by construction; what remains is a local alias name, and retiring a published name is a breaking removal from the barrel with no drift left to prevent. B/C are a maintainer's call, and the PR body says so rather than deciding it."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: packages/plugin-view/src/ObjectView.tsx's 'tree' branch ends with '...(viewOptions.tree || {})', which spreads whatever the block carries onto the object-tree NODE - so a stored titleField still lands on a node face that declares no such key (neither ObjectTreeSchema nor its zod mirror). Same tolerance layer as the three surviving dual-reads, and it is what made the removed rung unreachable. Successor: whoever takes the follow-up that retires the three dual-reads - that PR touches this exact line's neighbourhood.",
        "noted, not filed: the three surviving 'labelField || titleField' dual-reads (plugin-view, plugin-list, app-shell) read a key the protocol refuses and no face declares. Kept deliberately so stored view records keep resolving and objectui#6557's pin stays green; recorded in the PR body, in the views.ts docblock, and in a dedicated test case in tree-view-config-readers-8253.test.ts so the next session does not read 'the key is gone' as 'every read is gone'. Successor: the follow-up the card itself names. ⛔ Not filed separately - the card already scopes it.",
        "noted, not filed: check:spec-floors judges SYMBOL PRESENCE at the declared floor, not schema strictness, so it is green on a package whose published type depends on a strictness that only exists above its declared floor. Not a defect in the gate's own terms (its header scopes it to symbols); recorded here because it is the reason open_questions #2 is invisible to CI. Successor: whoever acts on open_questions #2.",
        "noted, not filed, FLAKE not defect: packages/plugin-grid/src/__tests__/formatHintedColumnRenderer-8920.test.tsx 'P: compound-cell prefix badge still draws the select renderer (must-not-change pin)' failed once in CI shard 2/4 with 'expected [] to include tel:+15551234567'. The assertion at :262 runs immediately after a waitFor on a DIFFERENT condition (container.textContent contains 'New'), so it races the hinted column's render. plugin-grid is not in my change set; the file passes locally on my tree 6/6 rc=0, and the CI re-run passed. Successor: none identified - it is a one-off observation about another package's test, and I did not measure a reproduction rate. ⛔ Not filed on a single un-reproduced occurrence.",
        "noted, not filed: the ORIGINAL PASS-WITH-FINDINGS verdict 5602172326 was accurate on findings 1 and 3 - I re-derived both. Its finding 1 correctly identifies that the ruling's cited read is off the flattened node, not the block, and that the ruling's line anchor was off by one; its finding 3 correctly identifies that the readers census checked each key as TEXT, so titleField was satisfied by the node read rather than by a block read. Recorded because the card frames the conflict as one verdict being right and one wrong, and that is not quite the shape: the PASS verdict measured correctly and then upheld a declaration on read-side evidence for a ruling that had asked a write-side question. Successor: the seat reading this card's closure."
      ]
    }

    Full detail, all controls, and both ablation transcripts are in the PR body: #9052

    Session session_01Jmxdo7bmeqCQHLSfmLVX9w. Generated by Claude Code.


    Generated by Claude Code

  7. os-warren commented on Sep 10, 2026

    @os-warren
    Collaborator

    Process finding from the contract review — routed to the decision box, ⛔ not adjudicated here

    The round-1 review of PR objectui#9052 (record 5625941898, ceiling tier, transcript-verified 123/123) landed a finding that is about this seat's conduct, not about the diff. Recording it on the card that exists to close the conflict, and adding it to the seat's decision box for the maintainer.

    It rejected both earlier framings of the two-verdict conflict as too kind

    This card framed it as one verdict right, one wrong. The objectui#9052 dev proposed accurate, then wrong one step up. The reviewer measured three things and rejected both:

    1. The PASS verdict asserted that deleting the titleField rung would redden objectui#6557's pin. False — 32/32 pass with the rung gone; that pin is on the app-shell fold.
    2. It measured spec 17.4.0 for the name collision and never for the key, although 17.4.0 had been on npm since 03:57Z and the contradicting review cited the refusal by file:line.
    3. Only then did it swap the read axis for the ruling's write axis.

    The finding, verbatim

    "A contract review that upholds a declared key without a parse-level reading against the protocol's accept set is incomplete at any tier, and two contradicting tier verdicts on one head are a real fork for the decision box, ⛔ not a clear on one's own subagent's PASS."

    ⛔ What this seat is NOT doing with it

    ⛔ Not adjudicating it. The second half is a rule about when a seat may clear carriers, and it says that on 2026-09-09 this seat should have taken the objectui#8828 fork to the maintainer instead of adopting its own subagent's PASS and clearing. That is a ruling on seat authority — 真正设计分叉照旧进决策箱,席内复核 ⛔ 不替代维护者裁定 — so it goes to the maintainer with the measurements above, ⛔ not into a dispatch.

    ⚠️ It also does not gate PR objectui#9052's landing: that head carries one verdict and no fork. The PR is enqueued.

    One related correction the same reviewer made to the record

    The objectui#8828 records described the two reviews as coming from "distinct sessions". That wording overstated the independence: subagent reviews in this lane are children of the same seat session, and independence rests on context isolation plus tier (contract-review.md:33-34), ⛔ not on session separation. Seven reviews today have now stated that reading from the charter text; this is the first to catch the prior records claiming more than the charter supports.


    Generated by Claude Code

  8. os-warren commented on Sep 10, 2026

    @os-warren
    Collaborator

    Release: MERGED — objectui#9052

    TreeViewConfig is derived from the spec and titleField is gone from the tree face on main.

    Landing commit 2f6b2bf1a97def0796bd202469a3f3dc69848319 · merged 2026-09-10T22:59:43Z · squash, parent c3a4273789.

    Two readings, ⛔ never a single API field

    1. Timeline, including the interruption:
    added_to_merge_queue     21:56:20Z
    removed_from_merge_queue 22:23:59Z   <- dequeued, CI_FAILURE
    added_to_merge_queue     22:28:48Z   <- re-投, head unchanged
    merged                   22:59:43Z
    
    1. origin/main by content — landing rc 0; pre-queue head 7c1e03efe… rc 1 (the control fires).

    Behaviour verification — and one reading that came back WRONG until I read it properly

    reading on main @ a2eb62fd08 value
    types/src/views.ts:147 export type TreeViewConfig = NonNullable<SpecListView['tree']>; — derived, not a hand copy
    titleField inside that type 0
    schema.titleField in plugin-tree/src/ObjectTree.tsx, non-comment lines 0
    CONTROL — same comment-stripping filter over schema. in that file 12, so the filter does not simply drop everything
    CONTROL — the canonical rung survives: :163 labelField: fieldKey(schema.labelField ?? nested.labelField) ?? 'name' present
    CONTROL — tolerant dual-reads deliberately kept: titleField in plugin-view/src/ObjectView.tsx 14
    census pin tree-view-config-readers-8253.test.ts present

    ⚠️ My first probe measured schema.titleField = 2 and I did not report that as a failure. A whole-file grep counts the docblock that explains the removal — and that docblock is deliberately placed above the function because the census test slices the function body, so prose about a deleted read inside the slice would read as the read itself. The dev wrote that reason into the comment. The right measurement is non-comment lines, which is 0. ⛔ An unexpected number is a reason to read the source, not to report a discrepancy.

    Review

    Round 1 VERDICT: PASS WITH FINDINGS, no blocking finding — record 5625941898, ceiling tier verified 123/123 with a positive control on the counter.

    ⭐ It rejected both earlier framings of the two-verdict conflict this card exists to close. The card said one verdict was right and one wrong; the dev proposed "accurate, then wrong one step up". The reviewer measured three things and rejected both: the PASS verdict claimed deleting this rung would redden objectui#6557's pin (false — 32/32 pass with it gone); it measured spec 17.4.0 for the name collision but never for the key, though 17.4.0 had been on npm since 03:57Z; and only then swapped the read axis for the ruling's write axis.

    Its process finding is recorded on this card and in the seat's decision box, ⛔ not adjudicated by this seat: "a contract review that upholds a declared key without a parse-level reading against the protocol's accept set is incomplete at any tier, and two contradicting tier verdicts on one head are a real fork for the decision box, ⛔ not a clear on one's own subagent's PASS."

    It also corrected the objectui#8828 records, which described the two reviews as coming from "distinct sessions" — that overstated the independence, which rests on context isolation plus tier.

    The dequeue

    22:23:59Z, CI_FAILURE, on a queue build whose own 19 workflow runs were all success. No failing queue-branch run was found in the window ⇒ the cause was recorded as NOT MEASURED, ⛔ not called a flake and ⛔ not blamed on a batch-mate I could not name. Re-投 once, head unchanged; it merged on the second pass.

    Routed, ⛔ not ridden in

    .changeset/8253-export-tree-view-config.md says "titleField is declared, not deleted" and "deleting the read would have reddened its pin" — the first falsified by this merge, the second false when written. Both are on objectui#9065 as its strong-class site and must land before the next @object-ui/types release.

    Card closed by the PR's keyword; pm:dispatched stripped in this same act.


    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

domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions