Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/fix-explain-ownership-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
"@objectstack/cli": patch
---

fix(cli): `os explain object` documented `ownership` with the wrong allowed values (#3244)

The schema catalog described the object `ownership` field as the package
*contribution* kind (`"own" | "extend"`, the `ObjectOwnershipEnum` set via
`registerObject`). But `ObjectSchema.ownership` is the **record-ownership
model** — `z.enum(['user', 'org', 'none'])` — a distinct concept the spec
explicitly warns not to conflate despite the shared word.

`os explain object` now prints:

ownership 'user' | 'org' | 'none' Record-ownership model: user (default,
injects a reassignable owner_id) | org | none (no per-record owner).
Distinct from the package own/extend contribution kind.

A regression test (`packages/cli/test/commands.test.ts`) pins the documented
values to the record-ownership enum so the two concepts can't drift back
together. Found during the #1880 docs implementation-accuracy audit.
4 changes: 2 additions & 2 deletions packages/cli/src/commands/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface SchemaInfo {
docsPath: string;
}

const SCHEMAS: Record<string, SchemaInfo> = {
export const SCHEMAS: Record<string, SchemaInfo> = {
object: {
name: 'Object',
description: 'Defines a data entity in the ObjectStack data model. Objects contain fields, enable capabilities, and form the foundation of the metadata-driven platform.',
Expand All @@ -34,7 +34,7 @@ const SCHEMAS: Record<string, SchemaInfo> = {
{ name: 'label', type: 'string', description: 'Human-readable display name' },
{ name: 'pluralLabel', type: 'string', description: 'Plural display name' },
{ name: 'description', type: 'string', description: 'Documentation for the object' },
{ name: 'ownership', type: '"own" | "extend"', description: 'Whether this object is owned or extended' },
{ name: 'ownership', type: "'user' | 'org' | 'none'", description: 'Record-ownership model: user (default, injects a reassignable owner_id) | org | none (no per-record owner). Distinct from the package own/extend contribution kind.' },
{ name: 'enable', type: 'ObjectCapabilities', description: 'Feature flags (trackHistory, apiEnabled, etc.)' },
{ name: 'icon', type: 'string', description: 'Icon identifier for UI display' },
],
Expand Down
21 changes: 20 additions & 1 deletion packages/cli/test/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Info from '../src/commands/info';
import Generate from '../src/commands/generate';
import Lint from '../src/commands/lint';
import Diff from '../src/commands/diff';
import Explain from '../src/commands/explain';
import Explain, { SCHEMAS } from '../src/commands/explain';

describe('CLI Commands (oclif)', () => {
it('should have compile command', () => {
Expand Down Expand Up @@ -67,3 +67,22 @@ describe('CLI Commands (oclif)', () => {
expect(Explain.description).toContain('explanation');
});
});

describe('os explain — schema catalog accuracy', () => {
// Regression guard for #3244: `os explain object` used to document the
// `ownership` field as the package-contribution kind (`"own" | "extend"`),
// which is a DISTINCT concept (`ObjectOwnershipEnum`, set via registerObject).
// The real `ObjectSchema.ownership` field is the record-ownership model —
// `z.enum(['user','org','none'])` — see packages/spec/src/data/object.zod.ts.
it('documents object.ownership as the record-ownership model, not the own/extend contribution kind (#3244)', () => {
const ownership = SCHEMAS.object.optional.find((f) => f.name === 'ownership');
expect(ownership, 'object schema should document an `ownership` field').toBeDefined();

// The type string must enumerate exactly the record-ownership enum values.
const tokens = (ownership!.type.match(/'[^']+'|"[^"]+"/g) ?? []).map((t) => t.slice(1, -1));
expect(new Set(tokens)).toEqual(new Set(['user', 'org', 'none']));

// …and must never regress back to the contribution-kind values.
expect(ownership!.type).not.toBe('"own" | "extend"');
});
});
Loading