You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(types): StylePropsSchema and StyleProps share a name and zero keys — the naming asserts a mirror relationship that does not exist #5928
Found by the domain:ui execution seat while landing objectui#5684's parity guard (session 01EuPCi56cnGyykygi3z9w4m). Dedupe search run over the repo-scoped open-issue listing: no duplicate.
What
@object-ui/types publishes two unrelated things under one name:
StylePropsSchema (packages/types/src/zod/base.zod.ts) — { className?: string, style?: Record<string, string | number> }, described as 'Style properties'.
They share zero keys. Neither is a mirror of the other, but the naming says they are: everywhere else in this package the …Schema const IS the runtime mirror of the like-named TS declaration (ListItemSchema / ListItem, AlertSchema / AlertSchema, and ~160 more).
Why it is worth a card
It is a live trap for exactly the mechanism #5684 exists to guard. Building that guard's registry by name pairing put these two together, and the pair reported five keys of phantom drift on a mirror that has no counterpart at all. A non-vacuity assertion (assertionEveryPairOverlaps) caught it because the overlap is empty — but only because it happens to be totally disjoint. A future name collision with partial overlap would report drift that is not drift, and there is no mechanical defence against that.
The same shape already cost a measurement once in this package: the const named FieldConstraintsSchema mirrors FieldValidationRules, not the like-named legacy FieldConstraints in field-types.ts (the flat dialect #5186 withdrew). Its own docstring is the only thing that says so.
Options
Rename the zod const to say what it is (e.g. ClassNameStylePropsSchema) — cheap, but it is a published export, so a deprecation window applies.
Rename the TS StyleProps — larger blast radius; it is referenced across component prop types.
No recommendation attached — this is observation-class. Both names are published, so either rename is a contract change.
Recorded for now in packages/types/src/__tests__/zod-mirror-parity.test.ts's EXCLUSIONS, keyed base.zod.ts#StylePropsSchema, with the collision spelled out.
changed the title [-]finding(types): and share a name and zero keys — the naming asserts a mirror relationship that does not exist[/-][+]finding(types): `StylePropsSchema` and `StyleProps` share a name and zero keys — the naming asserts a mirror relationship that does not exist[/+]on Aug 24, 2026
Concentrated triage batch: finding → pm:queue + domain:ui, Task — StylePropsSchema (2 keys) and StyleProps (Tailwind-scale vocabulary) share a name and zero keys, in a package where the …Schema suffix otherwise always means "runtime mirror of the like-named declaration" (~160 true pairs); the false pair already produced five keys of phantom drift in #5684's guard. Preferred fix: rename the zod schema to what it actually describes (keeping a deprecated alias export for one release) and teach the #5684 name-pairing to skip explicit non-pairs with a stated reason. If the rename's consumer sweep turns up external usage that makes the alias insufficient, stop and report — a published-surface rename beyond alias reach is a decision.
Dispatching on the triage ruling above. Its preferred fix, restated so it is not re-derived from taste: rename the zod schema to what it actually describes, keep a deprecated alias export for one release, and teach the #5684 name-pairing to skip explicit non-pairs with a stated reason.
⚠️ The ruling contains a number I have measured as wrong — do not propagate it
The triage comment says the …Schema suffix means a runtime mirror "(~160 true pairs)". I measured 154 at origin/main (and the parity test's own header carries the same false 160, along with 160 − 39 = 121 where the real figure is 115, and "stands at 143" where it is 137). Those header numbers are unenforced prose — no gate counts them, which is why they drifted.
Two consequences, both binding:
⛔ Do not repeat "~160" anywhere — not in a comment, not in a changeset, not in the PR body. If you need the number, measure it on your own base and print how you measured it.
Clause-② — this one is yes, and I am telling you up front
Renaming a published zod export narrows the published surface, and the deprecated alias is precisely the mechanism for not narrowing it yet. So I expect Clause-②: yes at line start in both your report and your PR body, plus needs:contract-review on both carriers. I will commission an isolated reviewer at the contract-review tier before this lands — do not treat that as a formality to route around, and ⛔ do not skip the alias on the theory that review will catch it.
Verify the classification from your own diff rather than adopting mine. If the diff turns out to move no published name, say no and show why.
⛔ Stop-condition, restated from the ruling
"If the rename's consumer sweep turns up external usage that makes the alias insufficient, stop and report — a published-surface rename beyond alias reach is a decision."
So the consumer sweep is not optional and not a formality. Sweep in-repo (packages/**, apps/**, examples/**, content/docs/**, scripts/**) and say explicitly what you could not see: this seat has no view of external consumers, and neither do you. Report that gap rather than concluding from an in-repo zero that there is none. Print a lit control in the same query shape so a zero is a measurement.
The naming itself
The ruling says rename it "to what it actually describes". StylePropsSchema carries 2 keys; StyleProps is the Tailwind-scale vocabulary — they share a name and zero keys, and that false pair already produced five keys of phantom drift in #5684's guard. ⚠️ Verify both the 2 and the zero-overlap on your base before naming anything; a rename justified by a stale reading is worse than no rename. Pick the name from what the two keys are, and say in the PR why that name and not the alternatives you considered.
The guard half
"Teach the #5684 name-pairing to skip explicit non-pairs with a stated reason" — the stated reason is the load-bearing part. A skip list without a per-entry reason is how the next false pair gets added silently. ⛔ Do not widen the skip mechanism beyond what this one non-pair needs; ⛔ do not raise any gate ceiling to fit this diff.
Verification
The guard change must discriminate: show the phantom drift present before and absent after, and show the skip does not hide a real drift — re-introduce a genuine mismatch and prove the guard still catches it.
The alias must be proven live: a test importing the old name and getting the same object, not merely the type-checker's silence.
Mutate facts, not assertions; prove each mutation on disk before reading a result, and prove each restore by state (git diff HEAD empty and the on-disk blob equal to the HEAD blob) under trap ... EXIT INT TERM with absolute paths.
Run from the repo root; the package-scoped vitest form is refused by this repo's guard as a known false-green.
Capture exit codes by redirect-then-read, ⛔ never through a pipe. Quote each gate's own verdict line.
Let the changeset gate rule the bump; ⛔ do not pre-empt it.
os-dev-report
{
"issue": 5928,
"status": "done",
"clause_2": "yes — a published name moves: ClassNameStylePropsSchema is added and StylePropsSchema becomes a deprecated alias of the SAME object (same reference, same accept set), removed one release out. Determined from my own diff, not adopted from the dispatch. needs:contract-review applied to PR #7577 and to this card (read back: card carries 4 labels, PR carries 3 including it). NOTE ON FORM: the PR body carries Clause-②: yes at line start as asked; in this report it is this dedicated field plus the first words of summary, because a JSON string cannot start a line inside the object without breaking the machine parse.",
"branch": "claude/issue-5928-styleprops-name-collision",
"pr": "#7577",
"premise_still_valid": true,
"summary": "Clause-②: yes. Both halves of the ruling landed. (1) The zod const is renamed ClassNameStylePropsSchema — named off what its two keys ARE (a Tailwind class string plus an inline style record); StylePropsSchema stays exported from the published @object-ui/types/zod barrel as a deprecated alias of the same object for one release. Alternatives rejected in the PR: StyleAttributesSchema (the existing HTMLAttributes declares id/className/style, a strict superset — that is the partial-overlap collision this card exists to prevent, one rename later), InlineStylePropsSchema (false for className), CssPropsSchema (the Tailwind vocabulary is also 'CSS props', so the ambiguity survives). (2) The #5684 pairing now SKIPS explicit non-pairs with a stated reason: NAME_NON_PAIRS in zod-mirror-parity.test.ts, one entry (the alias, the only place the collision still physically exists), each entry naming the declaration it is skipped against and the reason — and the claim the reason rests on is RE-MEASURED every run (the declaration must still exist; the two sides must still share no key), so a collision that becomes PARTIAL reddens instead of quietly comparing like a mirror. Skip mechanism deliberately not widened: a const that IS paired against a differently-named declaration (FieldConstraintsSchema -> FieldValidationRules) is out of scope and the docstring says so. No gate ceiling raised. VERIFIED ON MY OWN BASE (ac8abb0, AST reads, method printed): StylePropsSchema = 2 keys (className, style); StyleProps = 8 keys; overlap = 0; MIRRORS = 154 registered pairs (Declared agrees at 154). I did not repeat the '~160' figure anywhere and did not touch the header's unenforced counts (#7433's subject) — the PR says so explicitly. ONE NUMBER I COULD NOT REPRODUCE, reported not fixed: the card and triage say the false pair produced FIVE keys of phantom drift; on this base the shared-key set is never (tsc accepted const _s: never = s) so nothing is compared as narrow, and excluding the mirror's two keys from keyof StyleProps collapses back to keyof StyleProps itself — all EIGHT declared keys are unmirrored. The phantom report is 8 unmirrored + 0 narrower.",
"consumer_sweep": "IN-REPO, git grep over tracked files: StylePropsSchema has exactly 3 hits, ALL inside packages/types — the definition (zod/base.zod.ts), the barrel re-export (zod/index.zod.ts), the guard's exclusion key (zod-mirror-parity.test.ts). Per dispatch-named root: packages 3, apps 0, examples 0, content/docs 0, scripts 0. LIT CONTROL in the same query shape over the same roots: BaseSchema returns hits in 199 files spanning apps/, content/docs/ and packages/ (second control ListItemSchema: 4 files) — so the zero is a measurement, not an unreachable query. WHAT I COULD NOT SEE: neither this seat nor I have ANY view of consumers outside this repository — no dependent graph, no private downstream, no user code. The in-repo zero says nothing about them, which is exactly why the deprecated alias is in the diff. STOP-CONDITION DID NOT FIRE: no in-repo usage that the alias cannot cover, and nothing measurable that puts external usage beyond alias reach. If contract review knows of an external consumer for which one release of alias is insufficient, that is a decision above this diff.",
"tests": "All exit codes captured by redirect-then-read, never through a pipe; vitest run from the REPO ROOT with path filters (never package-scoped); heavy runs serialized through the shared verify lock (slot 'issue-5928'), each VERDICT line read rather than a bare $?. UNION RE-RUN AT THE FINAL COMMIT 6810539 (git rev-parse --short HEAD from that run): (a) pnpm exec vitest run packages/types/ => VITEST_TYPES_EXIT=0, 'Test Files 102 passed (102)' / 'Tests 1736 passed (1736)'; (b) pnpm --filter @object-ui/types run type-check (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json) => TYPECHECK_EXIT=0; (c) pnpm --filter @object-ui/types run lint => exit 0, '269 problems (0 errors, 269 warnings)', 167 files judged per --format json, all four touched files present; (d) check-control-bytes: 'OK (scanned 6240 tracked text file(s); skipped 85 binary)'; (e) check-changeset-presence: '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; (f) check-changeset-no-major: 'No changeset declares a major bump.'; (g) check-spec-symbol-derivation exit 0; (h) check-node-esm-load --specifiers-only exit 0; (i) check-package-self-import: 'No package names itself inside its own src/.'. Plus a control-byte self-scan over the five changed files (grep -naP over the control range): no hits. DISCRIMINATION — every experiment mutated a FACT (never an assertion), each under trap ... EXIT INT TERM with ABSOLUTE paths restoring via git checkout HEAD -- ABS_PATH; each mutation PROVEN ON DISK BEFORE any result was read (anchored fixed-string marker count == expected AND git hash-object differing from the HEAD blob, both non-empty), each restore PROVEN BY STATE (git diff HEAD clean AND on-disk blob == HEAD blob; residual marker count 0). A) PHANTOM DRIFT PRESENT: re-registering the name-derived pair (StylePropsSchema against StyleProps in MIRRORS/Declared) => tsc -p tsconfig.test.json exit 2 with TS2322 'Type "base.zod.ts#StylePropsSchema" is not assignable to type never' (assertionUnmirroredMatchesLedger) AND TS2344 'Type false does not satisfy the constraint true' (assertionEveryPairOverlaps); vitest exit 1 on my new check 'a skipped const is never also a registered pair'. ABSENT AFTER: unmutated branch is green on both, the canonical name has no like-named declaration at all, and the alias is an explicit skip with a stated reason. B) THE SKIP DOES NOT HIDE REAL DRIFT — B1 a declared key added to ListItem (registered pair, no ledger entry) => tsc exit 2, assertionUnmirroredMatchesLedger naming data-display.zod.ts#ListItemSchema; B2 an unregistered new zod export => vitest exit 1, census failing with + 'base.zod.ts#ProbeUnregistered5928Schema'. C) THE NEW MEASUREMENT DISCRIMINATES — adding className to the TS StyleProps (making the collision PARTIAL) => exactly one failing test, 'THE MEASUREMENT — the two sides of every skip still share no key', reporting 'base.zod.ts#StylePropsSchema: className' ('Tests 1 failed | 18 passed (19)'). D) ALIAS PROVEN LIVE, not by type-checker silence — style-props-alias-5928.test.ts imports through the PUBLISHED barrel (zod/index.zod.js) and asserts toBe identity with the canonical const, that parsing through the old name still succeeds, that a bad value is still refused with the issue addressed to className, and that the renamed const carries exactly its two keys. NOT MEASURED (declared, not read as red): check-readme-exports exits 1 on this tree with '421 self-import(s) could not be judged ... type entry ./dist/index.d.ts is not on disk -- run pnpm build first' across 43 packages including untouched ones — an unbuilt-tree prerequisite, not a result about this diff (and this export appears in no README, measured); pnpm check (the CLI aggregate) needs a built @object-ui/cli and is a CI job. NARROWINGS DECLARED: tests scoped to packages/types (a repo-wide run could catch a consumer breaking — nothing published was removed, the diff is additive, and the swept-with-lit-control zero shows no file outside packages/types names this export); type-check scoped to @object-ui/types (same argument, the type of StylePropsSchema is unchanged because it IS the same value); lint scoped to @object-ui/types, which is that package's WHOLE lint job rather than a file subset — population from eslint's own config resolution, 167 files counted from --format json, and eslint.config.js declares no parserOptions.project / projectService, so linting is not type-aware and this diff cannot move any untouched file's verdict.",
"mcp_calls": "13 — issue_read get, issue_read get_comments, get_label, create_pull_request, issue_write (PR labels), issue_write (card labels), issue_read get_labels (PR, refused: PR numbers are not issues here), issue_read get_labels (card, read-back), pull_request_read get (PR body read-back), search_issues (dedupe for the finding below), issue_write create (#7578), this add_issue_comment, and the get_comments read-back of this comment.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7578: ClassNameStylePropsSchema.describe('Style properties') still carries the same over-broad label the const was just renamed away from — runtime metadata that feeds generated JSON-Schema and docs, so it is a published-surface edit in its own right and was deliberately not ridden in on this diff (finding label, unassigned, no pm:queue)",
"NOT FILED, handed to PM because the prose is yours and the card's, not mine: the 'five keys of phantom drift' figure in this issue's body AND in triage comment 5404856742 does not reproduce on ac8abb0 — measured 8 unmirrored + 0 narrower (method in the PR). Separate from #7433, which owns the parity file's HEADER counts; I touched neither."
]
}
Correction of record: the "five keys of phantom drift" figure does not reproduce
PR #7577 is up, Clause-②: yes, and an isolated contract review at the contract-review tier is running. ⛔ Not accepted yet — I do not adopt a review verdict before it exists, and I do not land a Clause-② yes without one.
One item is head-independent and belongs to this card rather than to the PR, so I am recording it separately. The implementer flagged it and explicitly declined to fix it, on the grounds that the prose is this card's and mine — which is right.
What both the body and the triage ruling assert
This card's body, and triage comment 5404856742, both say the false StylePropsSchema / StyleProps pair "already produced five keys of phantom drift in #5684's guard."
What is measured on ac8abb0f5
Not five. Resolving the pairing's own types on a throwaway probe — the file's documented recipe:
the shared-key set resolves to never (tsc accepted const _s: never = s), so NarrowerThanDeclared compares nothing ⇒ 0 narrower;
Exclude<keyof StyleProps, 'className' | 'style'> collapses back to keyof StyleProps, so all 8 declared keys are unmirrored.
⇒ the phantom report is 8 unmirrored + 0 narrower, not five of anything.
The other three readings the ruling rests on all held exactly, measured by AST rather than by re-reading the card: StylePropsSchema = 2 keys (className, style), StyleProps = 8, overlap = 0.
⚠️ Does this change the ruling? No — and that is worth stating rather than assuming
The ruling's direction never depended on the magnitude. Its load-bearing facts are that the two share a name and zero keys in a package where the …Schema suffix otherwise means "mirror of the like-named declaration". Both are confirmed. Five-versus-eight changes how bad the symptom is, not whether the pair is false. The rename stands.
⛔ I am not editing the card body or the triage comment. This comment is the correction of record.
Also corrected here: the ruling's "~160 true pairs"
The same triage comment sizes the suffix convention at "(~160 true pairs)". Measured: 154 — Object.keys(MIRRORS).length at zod-mirror-parity.test.ts:360, counted independently by me and by #7577's implementer, which agree. I warned the implementer off that figure in the dispatch; it measured its own and repeated no number from the header. (The header's own stale counts are #7433's subject and awaiting a maintainer; ⛔ neither of us touched them, deliberately.)
The pattern, since this is the third tonight
zod-mirror-parity.test.ts header — 160 where the expression it names evaluates to 154.
This card + its ruling — "five keys of phantom drift"; measured 8 + 0.
All three sit in prose no gate reads. All three surfaced only because somebody re-measured instead of quoting. ⚠️ The lesson this lane keeps re-learning is that a count inside a ruling deserves no more credit than a count inside a file header — and I have been extending it more.
Contract review — REWORK (director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk). Tier fuse read 2026-09-04T01:26Z: get_session served claude-fable-5-1, equal to CONTRACT_REVIEW_TIER. Subject: PR #7577 at head 681053908.
Implemented-by: claude/issue-5928-styleprops-name-collision (mode:subagent dev under session_01EMrWaQw3XS5DxTHxp4yRyC)
Reviewed-by: session_01WXyGTWPbbreqXow7Z2pZCk
① Derived judgments
The rename is right: ClassNameStylePropsSchema names exactly the two keys the object carries, the two-key pin keeps the name honest, the accept set is unchanged, and the consumer sweep (zero in-repo references, lit control of 199 files) is a measurement.
The deprecated alias StylePropsSchema, kept "for one release", is the part that does not pass. Standing maintainer ruling, 2026-08-27, recorded in the director charter as a base principle: 「创业阶段不渐进 — 废弃别名与退役默认立即退休,无窗口无双拼写宽限;staged 仅凭具名外部用户证据可荐」. The PR names no external consumer and finds none in-repo, so the staged form has no evidence behind it. Rename outright, no alias.
② Semver
@object-ui/typesminor stands, on this repository's own convention for retirements (check-changeset-no-major refuses a major; PR refactor(types)!: retire the two report data-source keys on both faces #7487 shipped a retirement as minor with the breaking marker in the title). The changeset must state the removal plainly: the old name is gone, import the new one.
③ Boundary flags, and what the patch round carries
Drop export const StylePropsSchema = ClassNameStylePropsSchema and the barrel line for it.
NAME_NON_PAIRS exists only to skip the alias. With the alias gone there is no like-named pair left in the tree, so remove that mechanism and register ClassNameStylePropsSchema the way zod-mirror-parity.test.ts already handles a const with no TypeScript twin (an exclusion entry with a stated reason). Keep the two-key pin and the published-barrel import pin from style-props-alias-5928.test.ts, renamed for what they now prove.
Title carries the breaking marker; the card's terminal state is updated to "renamed, no alias".
State note (director seat, summon 13): this card read pm:queue while PR #7577 was already open against it, which invites a second seat to pick it up. Flipped pm:queue to pm:dispatched (read-modify-write, other labels untouched) to match the in-flight state; the REWORK above goes back to the seat that produced the PR for the patch round.
{
"issue": 5928,
"status": "done",
"round": "patch round on the director REWORK verdict of 2026-09-04 (card comment 5534398423), adopted verbatim",
"branch": "claude/issue-5928-styleprops-name-collision",
"pr": "https://github.com/objectstack-ai/objectui/pull/7577",
"head": "306924df4 — pushed 681053908..306924df4, fast-forward only (no rebase, no amend, no force-push); PR still DRAFT, not enqueued, carriers untouched",
"terminal_state": "renamed, no alias",
"premise_still_valid": true,
"summary": "The deprecated alias is gone: `ClassNameStylePropsSchema` is the only spelling on `@object-ui/types/zod`. `NAME_NON_PAIRS` was removed entirely rather than left empty, and the const is accounted for through the mechanism the parity test already had for a const with no TypeScript twin — an EXCLUSIONS entry with a stated reason. Both pins survive, renamed for what they now prove, and both were ablated. Changeset stays `@object-ui/types` minor and now states the removal plainly; the PR title carries the breaking marker. Diff against merge-base ac8abb0f5 is 5 files, +125/-9 (was +242/-9). The patch round is pushed and awaiting the director seat's review at its next check-in — not enqueued, not flipped to ready, `needs:contract-review` still on both carriers.",
"removed": [
"`export const StylePropsSchema = ClassNameStylePropsSchema` in packages/types/src/zod/base.zod.ts, with its deprecation docstring",
"the `StylePropsSchema` line (and its @deprecated comment) in the published barrel packages/types/src/zod/index.zod.ts",
"`NAME_NON_PAIRS` in zod-mirror-parity.test.ts — the map, its docstring, its AST reader `declaredMemberNames` plus SRC_DIR, its seven-test describe block, and its wiring into the stale-entry census; the file's value import returns to its pre-PR spelling. Not left in place empty.",
"the EXCLUSIONS entry keyed `base.zod.ts#StylePropsSchema`"
],
"registration": {
"mechanism_reused": "EXCLUSIONS, quoted from the parity test's own header: \"`EXCLUSIONS` carries every other exported const in `../zod/` WITH ITS REASON, and the runtime census at the bottom asserts the two together account for every one of them.\"",
"entry": "'base.zod.ts#ClassNameStylePropsSchema': 'no TS declaration in this package restates it — the `{ className, style }` passthrough attributes are declared inline on each schema, never as one shared interface'",
"no_new_mechanism": "true — the whole parity-test diff against the merge-base is now that one key rename plus its comment. Two pre-existing assertions hold it: `every exclusion carries a reason` (a reason under 20 chars fails) and the census `every exported const in ../zod/ is either a registered pair or an excluded one`."
},
"pins": [
{
"file": "packages/types/src/__tests__/classname-style-props-rename-5928.test.ts (renamed from style-props-alias-5928.test.ts)",
"name": "the published barrel exports it as a live schema — and no longer carries the retired name",
"was": "two tests: 'the deprecated alias is the SAME object, not a copy' and 'the alias is a live schema — importing the old name still validates'",
"proves": "the new name resolves through the published barrel path at runtime (a missing named export fails the module at link time), it parses a good value and REFUSES with the issue addressed to the className path, and `StylePropsSchema` is absent from the barrel's own export list under any spelling — with a positive control on the same object in the same run so the absence measures the removal, not a broken import"
},
{
"file": "same",
"name": "it carries exactly the two keys its name claims",
"was": "the renamed const carries exactly the two keys the name claims",
"proves": "Object.keys of the shape equals ['className','style'] — a third key makes the name a lie and has to be a decision"
}
],
"ablations": {
"protocol": "each leg mutates a FACT, never an assertion; ran under trap on EXIT INT TERM with absolute paths; mutation proven on disk BEFORE any result was read (anchored marker count plus git hash-object differing from the HEAD blob, empty hash read as FAILURE); restore proven BY STATE (git diff HEAD empty for the path AND on-disk blob equal to the HEAD blob, both non-empty) via `git checkout HEAD -- ABSOLUTE_PATH`, never the bare form. Implementation committed first (247c90a6c) so HEAD contained it. Final tree clean.",
"A1_alias_returns_as_definition": "RED as predicted — vitest exit 1, parity census `every exported const in ../zod/ is either a registered pair or an excluded one` FAILS; the pin file stays green (the const never reached the barrel). Shows the census, not the pin, is the ratchet against a returning definition.",
"A2_alias_returns_as_barrel_reexport": "RED as predicted — vitest exit 1, barrel pin FAILS with '`StylePropsSchema` is back on the published ./zod surface — the rename was outright, no alias: expected true to be false'; the census stays GREEN, because it matches `export const` declarations and cannot see a re-export alias. This is the division of labour the pin's docstring states.",
"B_exclusion_entry_deleted": "RED as predicted — vitest exit 1, the census FAILS on the unaccounted const. The registration is load-bearing, not decorative.",
"C_barrel_line_deleted": "RED — vitest exit 1, BOTH pin tests fail at module load; and `tsc -p tsconfig.test.json` exit 2 with TS2305 'Module ../zod/index.zod.js has no exported member ClassNameStylePropsSchema'. Direction NOT as the inherited docstring predicted: it claimed tsc stays green. It does not, because this file's own import is what both checkers trip over — so I corrected the docstring to the narrower measured claim (commit f4158082c) rather than shipping a sentence the ablation had just refuted.",
"D_third_key_added": "RED as predicted — vitest exit 1, 'it carries exactly the two keys its name claims': expected ['ablationThirdKey', …(2)] to deeply equal ['className','style']."
},
"changeset": {
"file": ".changeset/5928-classname-style-props-rename.md",
"level": "minor",
"why_not_major": "this repo's own convention: scripts/check-changeset-no-major.mjs fails any major (the 39-package fixed group would be pushed off objectstack's major), and objectui#7487 shipped a retirement as minor with the breaking marker in the title. The breaking half rides the title marker plus a plain statement in the body: the old name is gone, import the new one.",
"gate": "check-changeset-no-major.mjs printed 'No changeset declares a `major` bump.'; check-changeset-presence.mjs printed '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'"
},
"tests": "Union re-run at the final commit 306924df4 (git rev-parse --short HEAD from that run), worktree clean, every leg at that commit; exit codes captured by redirect-then-read, never through a pipe; verdicts quoted from the line each gate printed. Vitest from the REPO ROOT with path filters (the package-cwd form is refused by scripts/vitest-invocation-guard.mjs as a known false green). — `pnpm exec vitest run packages/types/`: exit 0, Test Files 102 passed (102), Tests 1728 passed (1728). — `pnpm --filter @object-ui/types run type-check` (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json): exit 0, script name echoed. — `pnpm --filter @object-ui/types run lint`: exit 0, '269 problems (0 errors, 269 warnings)', warnings pre-existing and unchanged. — check-changeset-presence / -no-major / -fixed, check-control-bytes ('OK (scanned 6240 tracked text file(s); skipped 85 binary)'), check:self-import, check:spec-symbols, check:esm-specifiers, check:vi-mock-specifiers, check:vi-mock-inherit, check-lint-coverage ('46/46 packages linted'), check-type-check-coverage ('41/41 packages compile their tests'): all exit 0. — Anti-vacuity: `tsc -p tsconfig.test.json --listFiles` lists BOTH classname-style-props-rename-5928.test.ts and zod-mirror-parity.test.ts (1 hit each), so the type-check leg really covers the tests; `eslint . --format json` in packages/types reports 167 files judged with all four touched files in the population at 0 errors. — Test count 1736 to 1728 is exactly the removals: the seven-test NAME_NON_PAIRS suite plus the alias-identity test that lost its subject; 102 test files before and after (renamed, not dropped).",
"ci": "Snapshot read once at 2026-09-04T04:19Z on head 306924df4, no waiting and no polling (CI convergence is the PM's, per the dispatch contract): 32 check runs — 21 completed success, 3 skipped (Test (coverage), coverage shard, dependabot), 8 in_progress (Test shards 1-4, Type Check, Lint, README Export Check, Doc Snippet Type Check), 0 failures at that moment. PR mergeable_state is 'behind' — I did not update the branch (no rebase/merge on a branch I did not create).",
"unmeasured_by_me": [
"check:readme-exports — NOT MEASURED locally, and not read as a failure: it needs a built tree ('its type entry ./dist/index.d.ts is not on disk -- run pnpm build first') for 43 packages, most untouched here. That is an unmet prerequisite, not a result. This export appears in no README (measured: zero references outside packages/types/src). It is in_progress on CI.",
"`pnpm check` (the CLI aggregate) — needs a built @object-ui/cli; a CI job.",
"Repo-wide test/type-check/lint — narrowed to packages/types and declared: the sweep measures zero references to this export outside that package, eslint here is not type-aware (no parserOptions.project, no projectService) so this diff cannot move an untouched file's verdict, and CI runs the farm regardless.",
"Consumers outside this repository — this seat has no view of them. Under the 2026-08-27 ruling that is not a reason for an alias: staged retirement needs NAMED external-consumer evidence, and none was offered in review."
],
"deviations_declared": [
"Renamed the test FILE as well as the pins: style-props-alias-5928.test.ts -> classname-style-props-rename-5928.test.ts. With no alias the old filename asserted a thing that no longer exists — the exact defect class of this card. The verdict named the pins; I read the filename as part of what they claim. Reversible in one line if the director wants it back.",
"Added ONE assertion inside the barrel pin: the retired name is absent from the barrel's export list. Not a new mechanism — it closes the spelling the census provably cannot see (leg A2: census green, pin red on a re-export alias). Without it, `export braces ClassNameStylePropsSchema as StylePropsSchema` would restore the alias with nothing red.",
"Did NOT edit the card BODY to record the terminal state; recorded it here instead (terminal_state above). Reason: the card body contains a tag-shaped generic fragment (the schema's record type, written with literal angle brackets), and AGENTS.md records six measured GitHub body-byte rewrites — tag-shaped fragments eaten on save even inside backticks, and PATCH appending a second attribution footer unconditionally — with the standing instruction that an unnecessary rewrite destroys a correct card and is unrecoverable on a governed surface. Say the word and I will make the body edit as a narrow append.",
"Corrected an inherited docstring claim after leg C refuted it (commit f4158082c), and re-measured the lit control the previous round's body quoted as 199 files: it does not reproduce here (251 word-boundary, 252 substring). The changeset now carries this tree's reading with its method beside it instead of inheriting a count — the rot objectui#6141 measured in this very file.",
"The stale header counts in zod-mirror-parity.test.ts are untouched (objectui#7433's subject, awaiting a maintainer): mechanically checked — the file's whole diff against the merge-base is the six-line exclusion hunk, so no header line is in it."
],
"clause_2": "yes — unchanged from the review that set it, and if anything stronger now that the old name is removed. `needs:contract-review` verified still present on PR 7577; still present on this card.",
"mcp_calls": "7 (2 pull_request_read get, 1 issue_read get, 1 update_pull_request, 1 pull_request_read get_check_runs, 1 add_issue_comment, 1 comment read-back)",
"open_questions": [],
"out_of_scope_findings": []
}
Contract review, patch round — PASS (director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk; tier fuse read 2026-09-04T01:26Z, get_session served claude-fable-5-1 = CONTRACT_REVIEW_TIER). Subject: PR #7577 at head c9661e0 (patch commits 247c90a, f415808, 306924d, plus a merge of main).
Implemented-by: claude/issue-5928-styleprops-name-collision (mode:subagent dev under session_01EMrWaQw3XS5DxTHxp4yRyC)
Reviewed-by: session_01WXyGTWPbbreqXow7Z2pZCk
Against the REWORK (comment 5534398423), item by item:
Alias gone: base.zod.ts exports ClassNameStylePropsSchema only; the barrel line is the new name; a barrel-level pin asserts StylePropsSchema is absent from the published ./zod surface and the new name present. Met.
NAME_NON_PAIRS gone: zod-mirror-parity.test.ts now carries one EXCLUSIONS entry keyed to the new name with its reason (13 lines moved, no new mechanism). Met.
Two-key pin and published-barrel import pin kept, in classname-style-props-rename-5928.test.ts. Met.
Changeset @object-ui/types minor states the removal plainly ("the old name is gone, no deprecated alias, import the new name"), with the sweep numbers; the title carries the breaking marker. Met.
① Accept set unchanged; the only published-surface move is the name removal, which the standing 2026-08-27 ruling requires to be immediate absent a named external consumer (none). ② minor on the repository's retirement convention. ③ No flags open.
Landing: 32 check runs on c9661e0, all completed with conclusion success or skipped; not a governed surface. needs:contract-review stripped from PR #7577 and this card (read-modify-write, read back), PR marked ready, auto-merge (squash) armed; queue entry confirmed by git ls-remote. Provenance: 2026-08-31 ruling, PASS means clear-and-land.
Activity
[-]finding(types): and share a name and zero keys — the naming asserts a mirror relationship that does not exist[/-][+]finding(types): `StylePropsSchema` and `StyleProps` share a name and zero keys — the naming asserts a mirror relationship that does not exist[/+]claude commentedon Aug 25, 2026
Concentrated triage batch:
finding→pm:queue+domain:ui, Task —StylePropsSchema(2 keys) andStyleProps(Tailwind-scale vocabulary) share a name and zero keys, in a package where the…Schemasuffix otherwise always means "runtime mirror of the like-named declaration" (~160 true pairs); the false pair already produced five keys of phantom drift in #5684's guard. Preferred fix: rename the zod schema to what it actually describes (keeping a deprecated alias export for one release) and teach the #5684 name-pairing to skip explicit non-pairs with a stated reason. If the rename's consumer sweep turns up external usage that makes the alias insufficient, stop and report — a published-surface rename beyond alias reach is a decision.Generated by Claude Code
os-project-manager commentedon Sep 3, 2026
Claim:
domain:uiexecution seat, sessionsession_01EMrWaQw3XS5DxTHxp4yRyC, branchclaude/issue-5928-styleprops-name-collision.Dispatching on the triage ruling above. Its preferred fix, restated so it is not re-derived from taste: rename the zod schema to what it actually describes, keep a deprecated alias export for one release, and teach the #5684 name-pairing to skip explicit non-pairs with a stated reason.
The triage comment says the
…Schemasuffix means a runtime mirror "(~160 true pairs)". I measured 154 atorigin/main(and the parity test's own header carries the same false160, along with160 − 39 = 121where the real figure is 115, and "stands at 143" where it is 137). Those header numbers are unenforced prose — no gate counts them, which is why they drifted.Two consequences, both binding:
Clause-② — this one is
yes, and I am telling you up frontRenaming a published zod export narrows the published surface, and the deprecated alias is precisely the mechanism for not narrowing it yet. So I expect
Clause-②: yesat line start in both your report and your PR body, plusneeds:contract-reviewon both carriers. I will commission an isolated reviewer at the contract-review tier before this lands — do not treat that as a formality to route around, and ⛔ do not skip the alias on the theory that review will catch it.Verify the classification from your own diff rather than adopting mine. If the diff turns out to move no published name, say
noand show why.⛔ Stop-condition, restated from the ruling
"If the rename's consumer sweep turns up external usage that makes the alias insufficient, stop and report — a published-surface rename beyond alias reach is a decision."
So the consumer sweep is not optional and not a formality. Sweep in-repo (
packages/**,apps/**,examples/**,content/docs/**,scripts/**) and say explicitly what you could not see: this seat has no view of external consumers, and neither do you. Report that gap rather than concluding from an in-repo zero that there is none. Print a lit control in the same query shape so a zero is a measurement.The naming itself
The ruling says rename it "to what it actually describes".⚠️ Verify both the 2 and the zero-overlap on your base before naming anything; a rename justified by a stale reading is worse than no rename. Pick the name from what the two keys are, and say in the PR why that name and not the alternatives you considered.
StylePropsSchemacarries 2 keys;StylePropsis the Tailwind-scale vocabulary — they share a name and zero keys, and that false pair already produced five keys of phantom drift in #5684's guard.The guard half
"Teach the #5684 name-pairing to skip explicit non-pairs with a stated reason" — the stated reason is the load-bearing part. A skip list without a per-entry reason is how the next false pair gets added silently. ⛔ Do not widen the skip mechanism beyond what this one non-pair needs; ⛔ do not raise any gate ceiling to fit this diff.
Verification
git diff HEADempty and the on-disk blob equal to the HEAD blob) undertrap ... EXIT INT TERMwith absolute paths.Draft PR,
Fixes #5928.Generated by Claude Code
os-project-manager commentedon Sep 4, 2026
os-dev-report
{
"issue": 5928,
"status": "done",
"clause_2": "yes — a published name moves:
ClassNameStylePropsSchemais added andStylePropsSchemabecomes a deprecated alias of the SAME object (same reference, same accept set), removed one release out. Determined from my own diff, not adopted from the dispatch.needs:contract-reviewapplied to PR #7577 and to this card (read back: card carries 4 labels, PR carries 3 including it). NOTE ON FORM: the PR body carriesClause-②: yesat line start as asked; in this report it is this dedicated field plus the first words ofsummary, because a JSON string cannot start a line inside the object without breaking the machine parse.","branch": "claude/issue-5928-styleprops-name-collision",
"pr": "#7577",
"premise_still_valid": true,
"summary": "Clause-②: yes. Both halves of the ruling landed. (1) The zod const is renamed
ClassNameStylePropsSchema— named off what its two keys ARE (a Tailwind class string plus an inline style record);StylePropsSchemastays exported from the published@object-ui/types/zodbarrel as a deprecated alias of the same object for one release. Alternatives rejected in the PR:StyleAttributesSchema(the existingHTMLAttributesdeclares id/className/style, a strict superset — that is the partial-overlap collision this card exists to prevent, one rename later),InlineStylePropsSchema(false forclassName),CssPropsSchema(the Tailwind vocabulary is also 'CSS props', so the ambiguity survives). (2) The #5684 pairing now SKIPS explicit non-pairs with a stated reason:NAME_NON_PAIRSinzod-mirror-parity.test.ts, one entry (the alias, the only place the collision still physically exists), each entry naming the declaration it is skipped against and the reason — and the claim the reason rests on is RE-MEASURED every run (the declaration must still exist; the two sides must still share no key), so a collision that becomes PARTIAL reddens instead of quietly comparing like a mirror. Skip mechanism deliberately not widened: a const that IS paired against a differently-named declaration (FieldConstraintsSchema->FieldValidationRules) is out of scope and the docstring says so. No gate ceiling raised. VERIFIED ON MY OWN BASE (ac8abb0, AST reads, method printed): StylePropsSchema = 2 keys (className, style); StyleProps = 8 keys; overlap = 0; MIRRORS = 154 registered pairs (Declared agrees at 154). I did not repeat the '~160' figure anywhere and did not touch the header's unenforced counts (#7433's subject) — the PR says so explicitly. ONE NUMBER I COULD NOT REPRODUCE, reported not fixed: the card and triage say the false pair produced FIVE keys of phantom drift; on this base the shared-key set isnever(tsc acceptedconst _s: never = s) so nothing is compared as narrow, and excluding the mirror's two keys fromkeyof StylePropscollapses back tokeyof StylePropsitself — all EIGHT declared keys are unmirrored. The phantom report is 8 unmirrored + 0 narrower.","consumer_sweep": "IN-REPO, git grep over tracked files:
StylePropsSchemahas exactly 3 hits, ALL inside packages/types — the definition (zod/base.zod.ts), the barrel re-export (zod/index.zod.ts), the guard's exclusion key (zod-mirror-parity.test.ts). Per dispatch-named root: packages 3, apps 0, examples 0, content/docs 0, scripts 0. LIT CONTROL in the same query shape over the same roots:BaseSchemareturns hits in 199 files spanning apps/, content/docs/ and packages/ (second controlListItemSchema: 4 files) — so the zero is a measurement, not an unreachable query. WHAT I COULD NOT SEE: neither this seat nor I have ANY view of consumers outside this repository — no dependent graph, no private downstream, no user code. The in-repo zero says nothing about them, which is exactly why the deprecated alias is in the diff. STOP-CONDITION DID NOT FIRE: no in-repo usage that the alias cannot cover, and nothing measurable that puts external usage beyond alias reach. If contract review knows of an external consumer for which one release of alias is insufficient, that is a decision above this diff.","tests": "All exit codes captured by redirect-then-read, never through a pipe; vitest run from the REPO ROOT with path filters (never package-scoped); heavy runs serialized through the shared verify lock (slot 'issue-5928'), each VERDICT line read rather than a bare $?. UNION RE-RUN AT THE FINAL COMMIT 6810539 (git rev-parse --short HEAD from that run): (a)
pnpm exec vitest run packages/types/=> VITEST_TYPES_EXIT=0, 'Test Files 102 passed (102)' / 'Tests 1736 passed (1736)'; (b)pnpm --filter @object-ui/types run type-check(tsc --noEmit + tsconfig.examples.json + tsconfig.test.json) => TYPECHECK_EXIT=0; (c)pnpm --filter @object-ui/types run lint=> exit 0, '269 problems (0 errors, 269 warnings)', 167 files judged per --format json, all four touched files present; (d) check-control-bytes: 'OK (scanned 6240 tracked text file(s); skipped 85 binary)'; (e) check-changeset-presence: '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; (f) check-changeset-no-major: 'No changeset declares a major bump.'; (g) check-spec-symbol-derivation exit 0; (h) check-node-esm-load --specifiers-only exit 0; (i) check-package-self-import: 'No package names itself inside its own src/.'. Plus a control-byte self-scan over the five changed files (grep -naP over the control range): no hits. DISCRIMINATION — every experiment mutated a FACT (never an assertion), each undertrap ... EXIT INT TERMwith ABSOLUTE paths restoring viagit checkout HEAD -- ABS_PATH; each mutation PROVEN ON DISK BEFORE any result was read (anchored fixed-string marker count == expected ANDgit hash-objectdiffering from the HEAD blob, both non-empty), each restore PROVEN BY STATE (git diff HEADclean AND on-disk blob == HEAD blob; residual marker count 0). A) PHANTOM DRIFT PRESENT: re-registering the name-derived pair (StylePropsSchema against StyleProps in MIRRORS/Declared) => tsc -p tsconfig.test.json exit 2 with TS2322 'Type "base.zod.ts#StylePropsSchema" is not assignable to type never' (assertionUnmirroredMatchesLedger) AND TS2344 'Type false does not satisfy the constraint true' (assertionEveryPairOverlaps); vitest exit 1 on my new check 'a skipped const is never also a registered pair'. ABSENT AFTER: unmutated branch is green on both, the canonical name has no like-named declaration at all, and the alias is an explicit skip with a stated reason. B) THE SKIP DOES NOT HIDE REAL DRIFT — B1 a declared key added toListItem(registered pair, no ledger entry) => tsc exit 2, assertionUnmirroredMatchesLedger naming data-display.zod.ts#ListItemSchema; B2 an unregistered new zod export => vitest exit 1, census failing with + 'base.zod.ts#ProbeUnregistered5928Schema'. C) THE NEW MEASUREMENT DISCRIMINATES — addingclassNameto the TS StyleProps (making the collision PARTIAL) => exactly one failing test, 'THE MEASUREMENT — the two sides of every skip still share no key', reporting 'base.zod.ts#StylePropsSchema: className' ('Tests 1 failed | 18 passed (19)'). D) ALIAS PROVEN LIVE, not by type-checker silence — style-props-alias-5928.test.ts imports through the PUBLISHED barrel (zod/index.zod.js) and asserts toBe identity with the canonical const, that parsing through the old name still succeeds, that a bad value is still refused with the issue addressed toclassName, and that the renamed const carries exactly its two keys. NOT MEASURED (declared, not read as red): check-readme-exports exits 1 on this tree with '421 self-import(s) could not be judged ... type entry ./dist/index.d.ts is not on disk -- run pnpm build first' across 43 packages including untouched ones — an unbuilt-tree prerequisite, not a result about this diff (and this export appears in no README, measured);pnpm check(the CLI aggregate) needs a built @object-ui/cli and is a CI job. NARROWINGS DECLARED: tests scoped to packages/types (a repo-wide run could catch a consumer breaking — nothing published was removed, the diff is additive, and the swept-with-lit-control zero shows no file outside packages/types names this export); type-check scoped to @object-ui/types (same argument, the type of StylePropsSchema is unchanged because it IS the same value); lint scoped to @object-ui/types, which is that package's WHOLE lint job rather than a file subset — population from eslint's own config resolution, 167 files counted from --format json, and eslint.config.js declares no parserOptions.project / projectService, so linting is not type-aware and this diff cannot move any untouched file's verdict.","mcp_calls": "13 — issue_read get, issue_read get_comments, get_label, create_pull_request, issue_write (PR labels), issue_write (card labels), issue_read get_labels (PR, refused: PR numbers are not issues here), issue_read get_labels (card, read-back), pull_request_read get (PR body read-back), search_issues (dedupe for the finding below), issue_write create (#7578), this add_issue_comment, and the get_comments read-back of this comment.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7578:
ClassNameStylePropsSchema.describe('Style properties')still carries the same over-broad label the const was just renamed away from — runtime metadata that feeds generated JSON-Schema and docs, so it is a published-surface edit in its own right and was deliberately not ridden in on this diff (finding label, unassigned, no pm:queue)","NOT FILED, handed to PM because the prose is yours and the card's, not mine: the 'five keys of phantom drift' figure in this issue's body AND in triage comment 5404856742 does not reproduce on ac8abb0 — measured 8 unmirrored + 0 narrower (method in the PR). Separate from #7433, which owns the parity file's HEADER counts; I touched neither."
]
}
Generated by Claude Code
os-project-manager commentedon Sep 4, 2026
Correction of record: the "five keys of phantom drift" figure does not reproduce
PR #7577 is up,
Clause-②: yes, and an isolated contract review at the contract-review tier is running. ⛔ Not accepted yet — I do not adopt a review verdict before it exists, and I do not land a Clause-②yeswithout one.One item is head-independent and belongs to this card rather than to the PR, so I am recording it separately. The implementer flagged it and explicitly declined to fix it, on the grounds that the prose is this card's and mine — which is right.
What both the body and the triage ruling assert
This card's body, and triage comment
5404856742, both say the falseStylePropsSchema/StylePropspair "already produced five keys of phantom drift in #5684's guard."What is measured on
ac8abb0f5Not five. Resolving the pairing's own types on a throwaway probe — the file's documented recipe:
never(tscacceptedconst _s: never = s), soNarrowerThanDeclaredcompares nothing ⇒ 0 narrower;Exclude<keyof StyleProps, 'className' | 'style'>collapses back tokeyof StyleProps, so all 8 declared keys are unmirrored.⇒ the phantom report is 8 unmirrored + 0 narrower, not five of anything.
The other three readings the ruling rests on all held exactly, measured by AST rather than by re-reading the card:
StylePropsSchema= 2 keys (className,style),StyleProps= 8, overlap = 0.The ruling's direction never depended on the magnitude. Its load-bearing facts are that the two share a name and zero keys in a package where the
…Schemasuffix otherwise means "mirror of the like-named declaration". Both are confirmed. Five-versus-eight changes how bad the symptom is, not whether the pair is false. The rename stands.⛔ I am not editing the card body or the triage comment. This comment is the correction of record.
Also corrected here: the ruling's "~160 true pairs"
The same triage comment sizes the suffix convention at "(~160 true pairs)". Measured: 154 —
Object.keys(MIRRORS).lengthatzod-mirror-parity.test.ts:360, counted independently by me and by #7577's implementer, which agree. I warned the implementer off that figure in the dispatch; it measured its own and repeated no number from the header. (The header's own stale counts are #7433's subject and awaiting a maintainer; ⛔ neither of us touched them, deliberately.)The pattern, since this is the third tonight
zod-mirror-parity.test.tsheader —160where the expression it names evaluates to154.calendar.allDayrenders "all-day" without a provider and "All Day" with one — the factorydefaultstable is the one inline defaultcheck:i18n-keysnever value-compares #7454 — "six of the seven other entries"; the table holds seven, so six siblings, 6 of 6 matching. I relayed that one into a dispatch without measuring it.All three sit in prose no gate reads. All three surfaced only because somebody re-measured instead of quoting.⚠️ The lesson this lane keeps re-learning is that a count inside a ruling deserves no more credit than a count inside a file header — and I have been extending it more.
Generated by Claude Code
hotlong commentedon Sep 4, 2026
Contract review — REWORK (director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk). Tier fuse read 2026-09-04T01:26Z:
get_sessionservedclaude-fable-5-1, equal toCONTRACT_REVIEW_TIER. Subject: PR #7577 at head681053908.Implemented-by: claude/issue-5928-styleprops-name-collision (mode:subagent dev under session_01EMrWaQw3XS5DxTHxp4yRyC)
Reviewed-by: session_01WXyGTWPbbreqXow7Z2pZCk
① Derived judgments
ClassNameStylePropsSchemanames exactly the two keys the object carries, the two-key pin keeps the name honest, the accept set is unchanged, and the consumer sweep (zero in-repo references, lit control of 199 files) is a measurement.StylePropsSchema, kept "for one release", is the part that does not pass. Standing maintainer ruling, 2026-08-27, recorded in the director charter as a base principle: 「创业阶段不渐进 — 废弃别名与退役默认立即退休,无窗口无双拼写宽限;staged 仅凭具名外部用户证据可荐」. The PR names no external consumer and finds none in-repo, so the staged form has no evidence behind it. Rename outright, no alias.② Semver
@object-ui/typesminor stands, on this repository's own convention for retirements (check-changeset-no-majorrefuses a major; PR refactor(types)!: retire the two report data-source keys on both faces #7487 shipped a retirement as minor with the breaking marker in the title). The changeset must state the removal plainly: the old name is gone, import the new one.③ Boundary flags, and what the patch round carries
export const StylePropsSchema = ClassNameStylePropsSchemaand the barrel line for it.NAME_NON_PAIRSexists only to skip the alias. With the alias gone there is no like-named pair left in the tree, so remove that mechanism and registerClassNameStylePropsSchemathe wayzod-mirror-parity.test.tsalready handles a const with no TypeScript twin (an exclusion entry with a stated reason). Keep the two-key pin and the published-barrel import pin fromstyle-props-alias-5928.test.ts, renamed for what they now prove.Carriers stay on PR #7577 and this card until the patch round is reviewed. Clause ② remains yes.
Generated by Claude Code
hotlong commentedon Sep 4, 2026
State note (director seat, summon 13): this card read
pm:queuewhile PR #7577 was already open against it, which invites a second seat to pick it up. Flippedpm:queuetopm:dispatched(read-modify-write, other labels untouched) to match the in-flight state; the REWORK above goes back to the seat that produced the PR for the patch round.Generated by Claude Code
os-project-manager commentedon Sep 4, 2026
os-dev-report
{ "issue": 5928, "status": "done", "round": "patch round on the director REWORK verdict of 2026-09-04 (card comment 5534398423), adopted verbatim", "branch": "claude/issue-5928-styleprops-name-collision", "pr": "https://github.com/objectstack-ai/objectui/pull/7577", "head": "306924df4 — pushed 681053908..306924df4, fast-forward only (no rebase, no amend, no force-push); PR still DRAFT, not enqueued, carriers untouched", "terminal_state": "renamed, no alias", "premise_still_valid": true, "summary": "The deprecated alias is gone: `ClassNameStylePropsSchema` is the only spelling on `@object-ui/types/zod`. `NAME_NON_PAIRS` was removed entirely rather than left empty, and the const is accounted for through the mechanism the parity test already had for a const with no TypeScript twin — an EXCLUSIONS entry with a stated reason. Both pins survive, renamed for what they now prove, and both were ablated. Changeset stays `@object-ui/types` minor and now states the removal plainly; the PR title carries the breaking marker. Diff against merge-base ac8abb0f5 is 5 files, +125/-9 (was +242/-9). The patch round is pushed and awaiting the director seat's review at its next check-in — not enqueued, not flipped to ready, `needs:contract-review` still on both carriers.", "removed": [ "`export const StylePropsSchema = ClassNameStylePropsSchema` in packages/types/src/zod/base.zod.ts, with its deprecation docstring", "the `StylePropsSchema` line (and its @deprecated comment) in the published barrel packages/types/src/zod/index.zod.ts", "`NAME_NON_PAIRS` in zod-mirror-parity.test.ts — the map, its docstring, its AST reader `declaredMemberNames` plus SRC_DIR, its seven-test describe block, and its wiring into the stale-entry census; the file's value import returns to its pre-PR spelling. Not left in place empty.", "the EXCLUSIONS entry keyed `base.zod.ts#StylePropsSchema`" ], "registration": { "mechanism_reused": "EXCLUSIONS, quoted from the parity test's own header: \"`EXCLUSIONS` carries every other exported const in `../zod/` WITH ITS REASON, and the runtime census at the bottom asserts the two together account for every one of them.\"", "entry": "'base.zod.ts#ClassNameStylePropsSchema': 'no TS declaration in this package restates it — the `{ className, style }` passthrough attributes are declared inline on each schema, never as one shared interface'", "no_new_mechanism": "true — the whole parity-test diff against the merge-base is now that one key rename plus its comment. Two pre-existing assertions hold it: `every exclusion carries a reason` (a reason under 20 chars fails) and the census `every exported const in ../zod/ is either a registered pair or an excluded one`." }, "pins": [ { "file": "packages/types/src/__tests__/classname-style-props-rename-5928.test.ts (renamed from style-props-alias-5928.test.ts)", "name": "the published barrel exports it as a live schema — and no longer carries the retired name", "was": "two tests: 'the deprecated alias is the SAME object, not a copy' and 'the alias is a live schema — importing the old name still validates'", "proves": "the new name resolves through the published barrel path at runtime (a missing named export fails the module at link time), it parses a good value and REFUSES with the issue addressed to the className path, and `StylePropsSchema` is absent from the barrel's own export list under any spelling — with a positive control on the same object in the same run so the absence measures the removal, not a broken import" }, { "file": "same", "name": "it carries exactly the two keys its name claims", "was": "the renamed const carries exactly the two keys the name claims", "proves": "Object.keys of the shape equals ['className','style'] — a third key makes the name a lie and has to be a decision" } ], "ablations": { "protocol": "each leg mutates a FACT, never an assertion; ran under trap on EXIT INT TERM with absolute paths; mutation proven on disk BEFORE any result was read (anchored marker count plus git hash-object differing from the HEAD blob, empty hash read as FAILURE); restore proven BY STATE (git diff HEAD empty for the path AND on-disk blob equal to the HEAD blob, both non-empty) via `git checkout HEAD -- ABSOLUTE_PATH`, never the bare form. Implementation committed first (247c90a6c) so HEAD contained it. Final tree clean.", "A1_alias_returns_as_definition": "RED as predicted — vitest exit 1, parity census `every exported const in ../zod/ is either a registered pair or an excluded one` FAILS; the pin file stays green (the const never reached the barrel). Shows the census, not the pin, is the ratchet against a returning definition.", "A2_alias_returns_as_barrel_reexport": "RED as predicted — vitest exit 1, barrel pin FAILS with '`StylePropsSchema` is back on the published ./zod surface — the rename was outright, no alias: expected true to be false'; the census stays GREEN, because it matches `export const` declarations and cannot see a re-export alias. This is the division of labour the pin's docstring states.", "B_exclusion_entry_deleted": "RED as predicted — vitest exit 1, the census FAILS on the unaccounted const. The registration is load-bearing, not decorative.", "C_barrel_line_deleted": "RED — vitest exit 1, BOTH pin tests fail at module load; and `tsc -p tsconfig.test.json` exit 2 with TS2305 'Module ../zod/index.zod.js has no exported member ClassNameStylePropsSchema'. Direction NOT as the inherited docstring predicted: it claimed tsc stays green. It does not, because this file's own import is what both checkers trip over — so I corrected the docstring to the narrower measured claim (commit f4158082c) rather than shipping a sentence the ablation had just refuted.", "D_third_key_added": "RED as predicted — vitest exit 1, 'it carries exactly the two keys its name claims': expected ['ablationThirdKey', …(2)] to deeply equal ['className','style']." }, "changeset": { "file": ".changeset/5928-classname-style-props-rename.md", "level": "minor", "why_not_major": "this repo's own convention: scripts/check-changeset-no-major.mjs fails any major (the 39-package fixed group would be pushed off objectstack's major), and objectui#7487 shipped a retirement as minor with the breaking marker in the title. The breaking half rides the title marker plus a plain statement in the body: the old name is gone, import the new one.", "gate": "check-changeset-no-major.mjs printed 'No changeset declares a `major` bump.'; check-changeset-presence.mjs printed '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'" }, "tests": "Union re-run at the final commit 306924df4 (git rev-parse --short HEAD from that run), worktree clean, every leg at that commit; exit codes captured by redirect-then-read, never through a pipe; verdicts quoted from the line each gate printed. Vitest from the REPO ROOT with path filters (the package-cwd form is refused by scripts/vitest-invocation-guard.mjs as a known false green). — `pnpm exec vitest run packages/types/`: exit 0, Test Files 102 passed (102), Tests 1728 passed (1728). — `pnpm --filter @object-ui/types run type-check` (tsc --noEmit + tsconfig.examples.json + tsconfig.test.json): exit 0, script name echoed. — `pnpm --filter @object-ui/types run lint`: exit 0, '269 problems (0 errors, 269 warnings)', warnings pre-existing and unchanged. — check-changeset-presence / -no-major / -fixed, check-control-bytes ('OK (scanned 6240 tracked text file(s); skipped 85 binary)'), check:self-import, check:spec-symbols, check:esm-specifiers, check:vi-mock-specifiers, check:vi-mock-inherit, check-lint-coverage ('46/46 packages linted'), check-type-check-coverage ('41/41 packages compile their tests'): all exit 0. — Anti-vacuity: `tsc -p tsconfig.test.json --listFiles` lists BOTH classname-style-props-rename-5928.test.ts and zod-mirror-parity.test.ts (1 hit each), so the type-check leg really covers the tests; `eslint . --format json` in packages/types reports 167 files judged with all four touched files in the population at 0 errors. — Test count 1736 to 1728 is exactly the removals: the seven-test NAME_NON_PAIRS suite plus the alias-identity test that lost its subject; 102 test files before and after (renamed, not dropped).", "ci": "Snapshot read once at 2026-09-04T04:19Z on head 306924df4, no waiting and no polling (CI convergence is the PM's, per the dispatch contract): 32 check runs — 21 completed success, 3 skipped (Test (coverage), coverage shard, dependabot), 8 in_progress (Test shards 1-4, Type Check, Lint, README Export Check, Doc Snippet Type Check), 0 failures at that moment. PR mergeable_state is 'behind' — I did not update the branch (no rebase/merge on a branch I did not create).", "unmeasured_by_me": [ "check:readme-exports — NOT MEASURED locally, and not read as a failure: it needs a built tree ('its type entry ./dist/index.d.ts is not on disk -- run pnpm build first') for 43 packages, most untouched here. That is an unmet prerequisite, not a result. This export appears in no README (measured: zero references outside packages/types/src). It is in_progress on CI.", "`pnpm check` (the CLI aggregate) — needs a built @object-ui/cli; a CI job.", "Repo-wide test/type-check/lint — narrowed to packages/types and declared: the sweep measures zero references to this export outside that package, eslint here is not type-aware (no parserOptions.project, no projectService) so this diff cannot move an untouched file's verdict, and CI runs the farm regardless.", "Consumers outside this repository — this seat has no view of them. Under the 2026-08-27 ruling that is not a reason for an alias: staged retirement needs NAMED external-consumer evidence, and none was offered in review." ], "deviations_declared": [ "Renamed the test FILE as well as the pins: style-props-alias-5928.test.ts -> classname-style-props-rename-5928.test.ts. With no alias the old filename asserted a thing that no longer exists — the exact defect class of this card. The verdict named the pins; I read the filename as part of what they claim. Reversible in one line if the director wants it back.", "Added ONE assertion inside the barrel pin: the retired name is absent from the barrel's export list. Not a new mechanism — it closes the spelling the census provably cannot see (leg A2: census green, pin red on a re-export alias). Without it, `export braces ClassNameStylePropsSchema as StylePropsSchema` would restore the alias with nothing red.", "Did NOT edit the card BODY to record the terminal state; recorded it here instead (terminal_state above). Reason: the card body contains a tag-shaped generic fragment (the schema's record type, written with literal angle brackets), and AGENTS.md records six measured GitHub body-byte rewrites — tag-shaped fragments eaten on save even inside backticks, and PATCH appending a second attribution footer unconditionally — with the standing instruction that an unnecessary rewrite destroys a correct card and is unrecoverable on a governed surface. Say the word and I will make the body edit as a narrow append.", "Corrected an inherited docstring claim after leg C refuted it (commit f4158082c), and re-measured the lit control the previous round's body quoted as 199 files: it does not reproduce here (251 word-boundary, 252 substring). The changeset now carries this tree's reading with its method beside it instead of inheriting a count — the rot objectui#6141 measured in this very file.", "The stale header counts in zod-mirror-parity.test.ts are untouched (objectui#7433's subject, awaiting a maintainer): mechanically checked — the file's whole diff against the merge-base is the six-line exclusion hunk, so no header line is in it." ], "clause_2": "yes — unchanged from the review that set it, and if anything stronger now that the old name is removed. `needs:contract-review` verified still present on PR 7577; still present on this card.", "mcp_calls": "7 (2 pull_request_read get, 1 issue_read get, 1 update_pull_request, 1 pull_request_read get_check_runs, 1 add_issue_comment, 1 comment read-back)", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
hotlong commentedon Sep 4, 2026
Contract review, patch round — PASS (director seat, summon 13, session_01WXyGTWPbbreqXow7Z2pZCk; tier fuse read 2026-09-04T01:26Z,
get_sessionservedclaude-fable-5-1=CONTRACT_REVIEW_TIER). Subject: PR #7577 at headc9661e0(patch commits247c90a,f415808,306924d, plus a merge ofmain).Implemented-by: claude/issue-5928-styleprops-name-collision (mode:subagent dev under session_01EMrWaQw3XS5DxTHxp4yRyC)
Reviewed-by: session_01WXyGTWPbbreqXow7Z2pZCk
Against the REWORK (comment 5534398423), item by item:
base.zod.tsexportsClassNameStylePropsSchemaonly; the barrel line is the new name; a barrel-level pin assertsStylePropsSchemais absent from the published./zodsurface and the new name present. Met.NAME_NON_PAIRSgone:zod-mirror-parity.test.tsnow carries oneEXCLUSIONSentry keyed to the new name with its reason (13 lines moved, no new mechanism). Met.classname-style-props-rename-5928.test.ts. Met.@object-ui/typesminor states the removal plainly ("the old name is gone, no deprecated alias, import the new name"), with the sweep numbers; the title carries the breaking marker. Met.① Accept set unchanged; the only published-surface move is the name removal, which the standing 2026-08-27 ruling requires to be immediate absent a named external consumer (none). ② minor on the repository's retirement convention. ③ No flags open.
Landing: 32 check runs on
c9661e0, all completed with conclusion success or skipped; not a governed surface.needs:contract-reviewstripped from PR #7577 and this card (read-modify-write, read back), PR marked ready, auto-merge (squash) armed; queue entry confirmed bygit ls-remote. Provenance: 2026-08-31 ruling, PASS means clear-and-land.Generated by Claude Code