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
57 changes: 57 additions & 0 deletions .changeset/prune-field-prune-orphan-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
"@objectstack/spec": minor
---

refactor(spec)!: finish the 2026-06 field prune — drop both orphaned value schemas, `DataQualityRulesSchema` and `ComputedFieldCacheSchema` (#3726, #3733)

**BREAKING** (the `!` marker and this changeset are the breaking-change record;
the train ships as the v17 major — see the `v17-rc-anchor` changeset), though
nothing in-tree or out could have depended on either meaningfully. Removed from
the `@objectstack/spec` public surface:

- `DataQualityRulesSchema` (const), `DataQualityRules` (type), `DataQualityRulesInput` (type) — #3726
- `ComputedFieldCacheSchema` (const), `ComputedFieldCache` (type) — #3733

and the published `data/DataQualityRules.json` / `data/ComputedFieldCache.json`
JSON Schemas.

**Why.** Five field keys were pruned in 2026-06 — `encryptionConfig`,
`maskingRule`, `auditTrail`, `cached` and `dataQuality` — as "dead in both
layers, aspirational governance with no runtime consumer" (see
`docs/audits/2026-06-dead-surface-disposition-plan.md`, P0/P2 field prune).
Three of the five took their value schemas with them. Two did not: `dataQuality`
and `cached` each lost their key from `FieldSchema` while
`DataQualityRulesSchema` / `ComputedFieldCacheSchema` stayed on the published API
surface and in the generated reference docs, with zero consumers anywhere in the
tree. The tombstone claimed "dead in both layers"; for these two it was true of
only one.

That middle state is the worst of the three available (key + schema + consumer /
none of them / schema only), and it failed quietly rather than loudly.
`FieldSchema` is **not** `.strict()`, so an author who found either type in the
reference docs and wrote `dataQuality: { uniqueness: true }` or
`cached: { enabled: true, ttl: 3600 }` got no error at all — the field parsed
clean and the key was silently stripped, leaving a rule that was declared in
source, absent from the contract, and enforced by nothing. That is the ADR-0104
failure class, the same one the `accept` / `maxSize` declarations were added to
close.

Each had its own sharp edge. `DataQualityRules.uniqueness`, described as "Enforce
unique values across all records", reads exactly like the platform-wide scope
that `unique: 'global'` actually provides (#3696), making it the option an author
was most likely to reach for by mistake. `ComputedFieldCache` was quieter and
therefore harder to catch: an author writing `ttl: 3600` on a formula field would
believe results were cached for an hour, get no error, and never see a signal
that nothing had happened.

**Migration.** There is no runtime behavior to migrate — neither schema was ever
reachable from `FieldSchema`, and neither had a consumer in-tree. For per-field
uniqueness use `unique` (`true` = unique within the tenant, `'global'` = unique
platform-wide; see #3696). `completeness`, `accuracy`, and computed-field caching
(`enabled` / `ttl` / `invalidateOn`) have no replacement; none was ever
implemented.

If field-level data-quality governance or computed-field caching is built for
real later, re-add the field key and its schema **together, with a consumer** —
the enforce side of enforce-or-remove (ADR-0049). A tombstone in `field.zod.ts`
records this so neither schema is restored on its own again.
30 changes: 2 additions & 28 deletions content/docs/references/data/field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Field Type Enum
## TypeScript Usage

```typescript
import { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, Field, FieldType, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';
import type { Address, ComputedFieldCache, CurrencyConfig, CurrencyValue, DataQualityRules, Field, FieldType, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';
import { Address, CurrencyConfig, CurrencyValue, Field, FieldType, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';
import type { Address, CurrencyConfig, CurrencyValue, Field, FieldType, LocationCoordinates, SelectOption, UniqueScope } from '@objectstack/spec/data';

// Validate data
const result = Address.parse(data);
Expand All @@ -38,19 +38,6 @@ const result = Address.parse(data);
| **formatted** | `string` | optional | Formatted address string |


---

## ComputedFieldCache

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **enabled** | `boolean` | ✅ | Enable caching for computed field results |
| **ttl** | `number` | ✅ | Cache TTL in seconds (0 = no expiration) |
| **invalidateOn** | `string[]` | ✅ | Field paths that invalidate cache (e.g., ["inventory.quantity", "pricing.base_price"]) |


---

## CurrencyConfig
Expand All @@ -76,19 +63,6 @@ const result = Address.parse(data);
| **currency** | `string` | ✅ | Currency code (ISO 4217) |


---

## DataQualityRules

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **uniqueness** | `boolean` | ✅ | Enforce unique values across all records |
| **completeness** | `number` | ✅ | Minimum ratio of non-null values (0-1, default: 0 = no requirement) |
| **accuracy** | `{ source: string; threshold: number }` | optional | Accuracy validation configuration |


---

## Field
Expand Down
5 changes: 0 additions & 5 deletions packages/spec/api-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,6 @@
"ClockTimeValueSchema (const)",
"ComparisonOperatorSchema (const)",
"Compatibility (type)",
"ComputedFieldCache (type)",
"ComputedFieldCacheSchema (const)",
"ConditionalValidation (type)",
"ConditionalValidationSchema (const)",
"ConsistencyLevel (type)",
Expand Down Expand Up @@ -252,9 +250,6 @@
"DataEngineUpdateOptionsSchema (const)",
"DataEngineUpdateRequestSchema (const)",
"DataEngineVectorFindRequestSchema (const)",
"DataQualityRules (type)",
"DataQualityRulesInput (type)",
"DataQualityRulesSchema (const)",
"DataTypeMapping (type)",
"DataTypeMappingSchema (const)",
"Datasource (type)",
Expand Down
2 changes: 0 additions & 2 deletions packages/spec/json-schema.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,6 @@
"data/BaseEngineOptions",
"data/CalendarDateValue",
"data/ClockTimeValue",
"data/ComputedFieldCache",
"data/ConditionalValidation",
"data/ConsistencyLevel",
"data/ContextToken",
Expand Down Expand Up @@ -700,7 +699,6 @@
"data/DataEngineUpdateOptions",
"data/DataEngineUpdateRequest",
"data/DataEngineVectorFindRequest",
"data/DataQualityRules",
"data/DataTypeMapping",
"data/Datasource",
"data/DatasourceCapabilities",
Expand Down
70 changes: 17 additions & 53 deletions packages/spec/src/data/field.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,56 +180,6 @@ export const AddressSchema = lazySchema(() => z.object({
formatted: z.string().optional().describe('Formatted address string'),
}));

/**
* Data Quality Rules Schema
* Defines data quality validation and monitoring for fields
*
* @example Unique SSN field with completeness requirement
* {
* uniqueness: true,
* completeness: 0.95, // 95% of records must have this field
* accuracy: {
* source: 'government_db',
* threshold: 0.98
* }
* }
*/
export const DataQualityRulesSchema = lazySchema(() => z.object({
/** Enforce uniqueness constraint */
uniqueness: z.boolean().default(false).describe('Enforce unique values across all records'),

/** Completeness ratio (0-1) indicating minimum percentage of non-null values */
completeness: z.number().min(0).max(1).default(0).describe('Minimum ratio of non-null values (0-1, default: 0 = no requirement)'),

/** Accuracy validation against authoritative source */
accuracy: z.object({
source: z.string().describe('Reference data source for validation (e.g., "api.verify.com", "master_data")'),
threshold: z.number().min(0).max(1).describe('Minimum accuracy threshold (0-1, e.g., 0.95 = 95% match required)'),
}).optional().describe('Accuracy validation configuration'),
}));

/**
* Computed Field Caching Schema
* Configuration for caching computed/formula field results
*
* @example Cache product price with 1-hour TTL, invalidate on inventory changes
* {
* enabled: true,
* ttl: 3600,
* invalidateOn: ['inventory.quantity', 'pricing.discount']
* }
*/
export const ComputedFieldCacheSchema = lazySchema(() => z.object({
/** Enable caching for this computed field */
enabled: z.boolean().describe('Enable caching for computed field results'),

/** Time-to-live in seconds */
ttl: z.number().min(0).describe('Cache TTL in seconds (0 = no expiration)'),

/** Array of field paths that trigger cache invalidation when changed */
invalidateOn: z.array(z.string()).describe('Field paths that invalidate cache (e.g., ["inventory.quantity", "pricing.base_price"])'),
}));

/**
* Field Schema - Best Practice Enterprise Pattern
*/
Expand Down Expand Up @@ -544,6 +494,23 @@ export const FieldSchema = lazySchema(() => z.object({
// the real channel is type:'secret'). See
// docs/audits/2026-06-dead-surface-disposition-plan.md (P0/P2 field prune):
// encryptionConfig, maskingRule, auditTrail, cached, dataQuality.
//
// Two of the five value schemas outlived their keys by a release — `dataQuality`'s
// (`DataQualityRulesSchema` + the `DataQualityRules` / `DataQualityRulesInput`
// types, #3726) and `cached`'s (`ComputedFieldCacheSchema` + `ComputedFieldCache`,
// #3733). Each key was gone from this object while its schema stayed on the
// published API surface and in the generated reference docs, so an author could
// still discover the shape and write it. This object is NOT `.strict()`, so that
// write did not fail loudly: it parsed clean and the key was silently stripped —
// the same ADR-0104 failure class as the pre-declaration `accept` / `maxSize`
// above (accepted in source, dropped in the contract, no feedback). Both schemas
// are removed as of #3733; all five keys are now dead in both layers, as the
// tombstone above always claimed.
//
// If field-level data-quality governance or computed-field caching is ever built
// for real, re-add the key and its schema TOGETHER, with a consumer — the enforce
// side of ADR-0049. Do not restore a schema on its own; that middle state is what
// both issues were filed about.

/** Layout & Grouping */
group: z.string().optional().describe('Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system")'),
Expand Down Expand Up @@ -642,9 +609,6 @@ export type Address = z.infer<typeof AddressSchema>;
export type CurrencyConfig = z.infer<typeof CurrencyConfigSchema>;
export type CurrencyConfigInput = z.input<typeof CurrencyConfigSchema>;
export type CurrencyValue = z.infer<typeof CurrencyValueSchema>;
export type DataQualityRules = z.infer<typeof DataQualityRulesSchema>;
export type DataQualityRulesInput = z.input<typeof DataQualityRulesSchema>;
export type ComputedFieldCache = z.infer<typeof ComputedFieldCacheSchema>;

/**
* Field Factory Helper
Expand Down
Loading