diff --git a/.changeset/prune-field-prune-orphan-schemas.md b/.changeset/prune-field-prune-orphan-schemas.md new file mode 100644 index 0000000000..9cb9f19db1 --- /dev/null +++ b/.changeset/prune-field-prune-orphan-schemas.md @@ -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. diff --git a/content/docs/references/data/field.mdx b/content/docs/references/data/field.mdx index 985cc4ed78..a6bef47abf 100644 --- a/content/docs/references/data/field.mdx +++ b/content/docs/references/data/field.mdx @@ -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); @@ -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 @@ -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 diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index 8ccefe2ffb..bf97e85b9a 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -192,8 +192,6 @@ "ClockTimeValueSchema (const)", "ComparisonOperatorSchema (const)", "Compatibility (type)", - "ComputedFieldCache (type)", - "ComputedFieldCacheSchema (const)", "ConditionalValidation (type)", "ConditionalValidationSchema (const)", "ConsistencyLevel (type)", @@ -252,9 +250,6 @@ "DataEngineUpdateOptionsSchema (const)", "DataEngineUpdateRequestSchema (const)", "DataEngineVectorFindRequestSchema (const)", - "DataQualityRules (type)", - "DataQualityRulesInput (type)", - "DataQualityRulesSchema (const)", "DataTypeMapping (type)", "DataTypeMappingSchema (const)", "Datasource (type)", diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index c3341a6bf7..d03abd2212 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -671,7 +671,6 @@ "data/BaseEngineOptions", "data/CalendarDateValue", "data/ClockTimeValue", - "data/ComputedFieldCache", "data/ConditionalValidation", "data/ConsistencyLevel", "data/ContextToken", @@ -700,7 +699,6 @@ "data/DataEngineUpdateOptions", "data/DataEngineUpdateRequest", "data/DataEngineVectorFindRequest", - "data/DataQualityRules", "data/DataTypeMapping", "data/Datasource", "data/DatasourceCapabilities", diff --git a/packages/spec/src/data/field.zod.ts b/packages/spec/src/data/field.zod.ts index 18b698f727..69b0896947 100644 --- a/packages/spec/src/data/field.zod.ts +++ b/packages/spec/src/data/field.zod.ts @@ -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 */ @@ -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")'), @@ -642,9 +609,6 @@ export type Address = z.infer; export type CurrencyConfig = z.infer; export type CurrencyConfigInput = z.input; export type CurrencyValue = z.infer; -export type DataQualityRules = z.infer; -export type DataQualityRulesInput = z.input; -export type ComputedFieldCache = z.infer; /** * Field Factory Helper