Skip to content

docs(field-types): give unique its real scope vocabulary - #14941

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-14901-unique-row-scope-vocabulary
Sep 3, 2026
Merged

docs(field-types): give unique its real scope vocabulary#14941
baozhoutao merged 1 commit into
mainfrom
claude/issue-14901-unique-row-scope-vocabulary

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Part of #14901 — this lands the type-vocabulary half only. The default half is deliberately not written; see "The default cell stays false" below. The card stays open to carry it.

The row

content/docs/data-modeling/field-types.mdx, Universal Field Properties:

-| `unique` | `boolean` | `false` | Enforce uniqueness |
+| `unique` | `boolean \| 'global' \| 'organization'` | `false` | Unique constraint **and its scope** (ADR-0120). ...

The contract has been boolean | 'global' | 'organization' since ADR-0120 D1, so the table hid two of the three spellings: a reader who trusted it could not express 'global' at all, and was contradicted by this same page's own worked example at :50, which passes unique: 'organization'.

Two things kept the edit anchored rather than invented:

  • The type cell matches the generated reference spelling for spelling. content/docs/references/data/field.mdx:64 (generated from FieldSchema, carries the AUTO-GENERATED banner) already renders `boolean \| 'global' \| 'organization'`. This hand-written row now agrees with it byte for byte in that cell, so the two pages cannot drift apart on the vocabulary.
  • The description points at the generated reference instead of restating the rule. That is this table's own existing convention — the sibling multiple row ends the same way, for the same reason: a hand-written row that restates a rule which regenerates elsewhere is a second copy waiting to go stale.

The default cell stays false — measured, not assumed

The card asked for a type-conditional default (false everywhere, 'organization' on autonumber) predicated on #13894. That premise does not hold on this tree, so writing it would have asserted a default the schema does not implement.

packages/spec built at 4b8f5f39, then FieldSchema.safeParse on the resolved output:

autonumber (unique omitted)        -> ok=true unique=false
text (unique omitted)              -> ok=true unique=false
autonumber unique:false            -> ok=true unique=false
autonumber unique:organization     -> ok=true unique="organization"
autonumber unique:global           -> ok=true unique="global"
autonumber unique:true             -> ok=true unique=true
unique:"tenant" accepted? false

An omitted unique on autonumber resolves to false, exactly as on text. Corroborating, in the source: unique: UniqueScopeSchema.default(false) is unconditional, and the only value-materializing hook on FieldSchema — the .overwrite() — deals exclusively with deleteBehavior and early-returns long before it could reach unique. superRefine cannot rewrite values. Independently, the generated reference page states the default as false with no type condition. #13894 is still open and is referenced nowhere in the tree.

All four accepted spellings parse and 'tenant' is refused, so the row's vocabulary is stated as measured.

packages/spec/** was not touched — the spec is the correct side of this disagreement, and the docs move to it.

Gates

Derived off the real change set from this worktree, not from a supplied list:

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack

34 commands, all run at 4b8f5f39 (the final commit — tree clean, nothing landed after the sweep). Every exit code captured before any pipe. 33 green, 1 NOT MEASURED, no findings.

The one not measured is scripts/check-test-completeness.mjs, which exits 3 with no argument and says so itself: it reads a saved test-run log, the derived family names it with no argument, and its own output instructs recording it as NOT MEASURED locally. It is not a red and there is nothing to fix.

Three gates first came back as refusals to measure rather than findings — check:doc-formula-expressions and check:doc-security-posture (exit 3, PREREQUISITE NOT MET: @objectstack/formula / @objectstack/lint not built) and check:skill-examples (exit 1, refusing to compute against a client-react dist with no declarations). I built those three packages and re-ran all three; each then measured green. They are counted green on the real reading, not on the refusal.

Sample verdict lines, quoted from the gates themselves:

✓ check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 404, content/blog 3.
✅ check-doc-anchors: 295 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
check-nul-bytes: OK (scanned 8102 text file(s) -- 8102 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
✓ route-spelling guard: population clean — every shape-matched literal spells its ledger row.

check-doc-anchors is the one that matters for the new link: the /docs/references/data/field target resolves.

No changeset: the only changed path is content/docs/**, consumed by apps/docs (private: true), so nothing publishes from a package. skip-changeset applied.

Also filed

#14940 — the autonumber callout on this same page (:485, :493) promises "guarantees a unique, ever-increasing business identifier", which the measurement above shows is not what a bare autonumber gets. Not swept in: the correct wording is a consequence of how #13894 is decided, not a mechanical substitution. Filed unassigned with Blocked-by: #13894.


🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

The Universal Field Properties row typed `unique` as `boolean` with the
one-line description "Enforce uniqueness". The contract has been
`boolean | 'global' | 'organization'` since ADR-0120 D1, so the table hid
two of the three spellings — a reader who trusted it could not express
`'global'` at all, and was contradicted by this same page's own example
at :50, which passes `unique: 'organization'`.

The type cell now matches the generated reference
(`content/docs/references/data/field.mdx`) spelling for spelling, and the
description points at the `FieldSchema` doc block rather than restating a
rule that regenerates elsewhere — the convention the sibling `multiple`
row on this table already uses.

The default cell is deliberately left at `false`. Measured against this
tree: `FieldSchema.parse({ type: 'autonumber', ... })` with `unique`
omitted resolves to `false`, exactly as `text` does. The card asked for a
type-conditional default (`'organization'` on `autonumber`) predicated on
 #13894, which has not landed. Documenting it would assert a default the
schema does not implement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Sep 3, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 3, 2026 11:39
@baozhoutao
baozhoutao enabled auto-merge September 3, 2026 11:39
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit b1d49b3 Sep 3, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14901-unique-row-scope-vocabulary branch September 3, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants