Uh oh!
There was an error while loading. Please reload this page.
docs(types): correct the UniquenessValidation deprecation prescription - #5614
Conversation
The `@deprecated` tag on `UniquenessValidation` pointed authors at two
spellings the platform no longer accepts, at exactly the moment they are
migrating off the old shape:
- `indexes[].partial` was retired in `@objectstack/spec` 17.0.0 (ADR-0049).
It is a tombstone the parse rejects at any value, so the "`partial` for a
scoped constraint" clause prescribed a key that cannot be declared.
- `{ fields, unique: true }` on `ObjectSchema.indexes` is the deprecated
positional spelling of `unique: 'global'` under ADR-0120 — lint
`unique/unscoped-declared-index` warns in 17.x and protocol 18 rejects it.
Measured against the installed `@objectstack/spec@17.0.0` rather than the
rc the report was written against. That measurement also refined the card:
the third spelling, field-level `unique: true`, is NOT deprecated — at field
level bare `true` is the positional spelling of `'organization'` and stays
valid. The two surfaces give the same token different meanings, so the
replacement names that difference instead of harmonizing them, which would
have shipped a fresh piece of false guidance.
Comment-only: all 38 changed lines are inside the block comment; no emitted
JavaScript behaviour changes.
Co-Authored-By: Claude <noreply@anthropic.com>…atch
`patch` rather than an empty frontmatter, decided by measurement rather than by
the "docs don't publish" heuristic: the JSDoc sits on an exported declaration,
so it ships. Built with the package's own `tsc` (`tsconfig.base.json` sets
`removeComments: false` deliberately), `dist/data-protocol.d.ts` moves
40218 -> 41781 bytes and carries the new prose.
The emitted `dist/data-protocol.js` is byte-identical either way (sha256
a3de34c5..., 207 bytes) — that module is types-only and its whole JS output is
the license banner plus `export {}`, so a comment on an erased `interface`
reaches the declaration file and nothing else. The `.d.ts` is what decides the
bump, and it changed.
Co-Authored-By: Claude <noreply@anthropic.com>✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-sales
commented
Aug 21, 2026
PM review — ACCEPT (card #4765)Gates. 22 named check runs read individually for The measurement changed the fix, and that is the whole value hereThe card treats
So a uniform rewrite would have swapped one piece of false guidance for another — on a card whose entire purpose is stopping false guidance from shipping on a Probe disciplineEvery zero paired with a control, and two of the controls are better than standard practice:
The retained claim from the original comment ( Changeset
Note
Cross-repo finding objectstack#10928 raised separately to the maintainer; it is out of this lane and not this PR's business. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#4765
Corrects the
@deprecatedprescription onUniquenessValidationinpackages/types/src/data-protocol.ts. Comment-only; no runtime behaviour.Re-measured against the installed spec, not the rc
The card was written against
@objectstack/spec@17.0.0-rc.6; GA17.0.0is pinned now, so every claim below was re-run against the installed package. Each probe is paired with a control, because a probe that can only come out one way is not a measurement.indexes[].partial— retired, rejected at any value. Confirmed:Same rejection at
partial: true, so it is a tombstone rather than a type error on one value. Control:{ fields: ['a'], zzz_not_a_key: 1 }parses green — the shape is.strip(), not strict, so the rejection is a targeted tombstone (partialis typedz.never()in the emitted shape) and not generic unknown-key behaviour.unique: true— deprecated on the index surface. Confirmed from the schema's own description text:Controls:
unique: 'global'andunique: 'organization'both parse green;unique: 'nonsense_scope'is rejected, so the union is genuinely discriminating.The measurement refined the card, and that changed the fix
The report treats
unique: trueas one uniform defect. It is not —uniqueis scope vocabulary shared by two surfaces on which the same baretruemeans different things, and the original sentence prescribed it on both in a single breath:truemeansindexes[].uniquefields— same materialization as'global'uniqueMeasured via the spec's own driver-facing helpers, which are the single source of truth the drivers read:
trueis neither flag, i.e. no organization key part is prepended — which is exactly why it is the positional spelling of'global'on the index surface. At field level the same value resolves throughisUniqueDeclared && !isGlobalUnique, i.e. per-organization.FieldSchema.safeParse({ type: 'text', unique: true })parses green.So two of the three prescribed spellings were wrong, and the third — field-level
unique: true— was correct all along. Rewriting bothunique: trueoccurrences the same way would have swapped one piece of false guidance for another. The replacement names the per-surface difference instead.The replacement
Prescribes
{ fields, unique: 'global' | 'organization' }for the index, keeps field-leveluniquevalid while flagging that the token is surface-dependent, and sends predicated constraints to the database layer (a runtime migration issuingCREATE UNIQUE INDEX … WHERE) rather than at a declarable key. It closes by naming what would falsify it —UniqueScopeSchemaandIndexSchemain@objectstack/spec— so the next reader checks the schemas rather than trusting the paragraph. This is the fourth piece of false guidance found in this campaign (strictness ledger finding 18); prose that cannot be checked is how the first three survived.The interface's own deprecation is untouched and still correct. Verified:
ValidationRuleSchemarejectstype: 'unique'at the discriminator (accepted:script,state_machine,format,cross_field,json_schema,conditional). Control — a fully green parse, so the probe was capable of passing:{ type: 'format', field: 'email', format: 'email', message: 'bad', name: 'r1' }parsessuccess = true.Comment-only, verified rather than asserted
Every one of the 38 changed lines in the
.tsfile was classified; lines not inside the block comment: 0.Changeset form:
patch, decided by the.d.tsMeasured with the package's real build (
tsc;tsconfig.base.jsonsetsremoveComments: falsedeliberately), building both sides from committed states:dist/data-protocol.d.tsdist/data-protocol.jsa3de34c5…The JSDoc sits on an exported declaration and reaches the declaration file, so consumer-visible API documentation changes and it is declared as
patch.Worth recording, since it cuts against the usual reasoning about
removeComments: false: the emitted JS is byte-identical here.data-protocol.tsis a types-only module whose entire JS output is the license banner plusexport {}— a comment attached to an erasedinterfacereaches the.d.tsand nothing else. The.d.tsis what decided the bump; the.jsis reported as measured, not as the general rule.Gates
Union re-run after the final commit, at
531561746. Exit codes captured before any pipe; each line is the gate's own verdict.check-control-bytescheck-changeset-presencecheck-changeset-no-majormajorbumpcheck-changeset-fixedcheck-type-check-coveragetype-check, 0 errors outstandingcheck-lint-coveragecheck-spec-symbol-derivationpnpm --filter @object-ui/types type-checktsc --noEmitplus the examples and test projectspnpm --filter @object-ui/types lintno-explicit-anypnpm exec vitest run packages/types/(repo root)check-phantom-dependenciescheck-package-self-importsrc/check-skills-pathsThe control-bytes gate was deliberately re-run after the changeset was committed: it scans tracked files, and on the first run the changeset was still untracked, so that run had not seen it. The count moves 4665 to 4666 across the two runs.
No test and no ablation is possible on this change. The diff is a comment; nothing executes it, so there is no assertion that could distinguish the old prose from the new one, and mutating it could not turn any suite red. The 460 passing tests are context, not evidence for this change. What stands in for a test is the parse measurement above — run against the installed spec, each probe paired with a control.
Declared narrowing. Repo-wide
pnpm lint(turbo run lint, 46 packages) was not run locally;@object-ui/typeswas linted in full instead. Why that cannot hide a failure: the population is read from eslint's own JSON output — 99 files, 0 errors, 255 warnings, with the changed file present in it and contributing 44 pre-existingno-explicit-anywarnings onanytypes, none of which a comment can create. The only channel by which this diff could reach another package's lint verdict is the emitted.d.ts, and type-aware linting is off:eslint.config.jshas zero hits forprojectService,project:, orparserOptions(controls on that same file:rules10 hits,files10 hits, so the search works). With no type information consulted, no untouched file's verdict can move. CI runs the full farm regardless.Generated by Claude Code
Generated by Claude Code