Skip to content

Commit f31cc8d

Browse files
authored
refactor(spec)!: 补完 2026-06 字段剪除 — 删除两个孤儿 value schema (#3726, #3733) (#3732)
Closes #3726. Closes #3733. The 2026-06 field prune removed five keys — encryptionConfig, maskingRule, auditTrail, cached, dataQuality — as "dead in both layers". Three took their value schemas with them; two did not. `DataQualityRulesSchema` and `ComputedFieldCacheSchema` stayed on the published API surface and in the generated reference docs long after their keys were gone from FieldSchema, with zero consumers anywhere in the tree. Removed: - DataQualityRulesSchema, DataQualityRules, DataQualityRulesInput (#3726) - ComputedFieldCacheSchema, ComputedFieldCache (#3733) - the published data/DataQualityRules.json and data/ComputedFieldCache.json api-surface.json goes 4383 -> 4381, exactly the 3 + 2 entries the two issues predicted. #3726's checklist recorded `cached` as already cleaned; it was not. That row is corrected here — the prune left two orphans, not one. One correction to #3726's stated rationale, verified by parse: FieldSchema is NOT .strict(). Authoring either shape succeeds and the key is silently dropped: FieldSchema.safeParse({ name: 'ssn', type: 'text', dataQuality: { uniqueness: true } }) -> parse success: true, dataQuality survived: false FieldSchema.safeParse({ name: 'total', type: 'formula', cached: { enabled: true, ttl: 3600 } }) -> parse success: true, cached survived: false Silent stripping is worse than the parse error the issue assumed: declared in source, absent from the contract, enforced by nothing, with no feedback — the ADR-0104 failure class the accept/maxSize declarations were added to close. The conclusion is unchanged; the case for removal is stronger. The json-schema ratchet (#2978) caught both retirements as designed; the manifest keys are removed as its deliberate-retirement step. A tombstone in field.zod.ts now covers both orphans and records that all five keys are finally dead in both layers, as it always claimed. Full build (71 tasks), test suite (132 tasks), all six spec drift checks and check:liveness green locally against current main; 16/16 CI checks green.
1 parent c5ff96d commit f31cc8d

5 files changed

Lines changed: 76 additions & 88 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
refactor(spec)!: finish the 2026-06 field prune — drop both orphaned value schemas, `DataQualityRulesSchema` and `ComputedFieldCacheSchema` (#3726, #3733)
6+
7+
**BREAKING** (the `!` marker and this changeset are the breaking-change record;
8+
the train ships as the v17 major — see the `v17-rc-anchor` changeset), though
9+
nothing in-tree or out could have depended on either meaningfully. Removed from
10+
the `@objectstack/spec` public surface:
11+
12+
- `DataQualityRulesSchema` (const), `DataQualityRules` (type), `DataQualityRulesInput` (type) — #3726
13+
- `ComputedFieldCacheSchema` (const), `ComputedFieldCache` (type) — #3733
14+
15+
and the published `data/DataQualityRules.json` / `data/ComputedFieldCache.json`
16+
JSON Schemas.
17+
18+
**Why.** Five field keys were pruned in 2026-06 — `encryptionConfig`,
19+
`maskingRule`, `auditTrail`, `cached` and `dataQuality` — as "dead in both
20+
layers, aspirational governance with no runtime consumer" (see
21+
`docs/audits/2026-06-dead-surface-disposition-plan.md`, P0/P2 field prune).
22+
Three of the five took their value schemas with them. Two did not: `dataQuality`
23+
and `cached` each lost their key from `FieldSchema` while
24+
`DataQualityRulesSchema` / `ComputedFieldCacheSchema` stayed on the published API
25+
surface and in the generated reference docs, with zero consumers anywhere in the
26+
tree. The tombstone claimed "dead in both layers"; for these two it was true of
27+
only one.
28+
29+
That middle state is the worst of the three available (key + schema + consumer /
30+
none of them / schema only), and it failed quietly rather than loudly.
31+
`FieldSchema` is **not** `.strict()`, so an author who found either type in the
32+
reference docs and wrote `dataQuality: { uniqueness: true }` or
33+
`cached: { enabled: true, ttl: 3600 }` got no error at all — the field parsed
34+
clean and the key was silently stripped, leaving a rule that was declared in
35+
source, absent from the contract, and enforced by nothing. That is the ADR-0104
36+
failure class, the same one the `accept` / `maxSize` declarations were added to
37+
close.
38+
39+
Each had its own sharp edge. `DataQualityRules.uniqueness`, described as "Enforce
40+
unique values across all records", reads exactly like the platform-wide scope
41+
that `unique: 'global'` actually provides (#3696), making it the option an author
42+
was most likely to reach for by mistake. `ComputedFieldCache` was quieter and
43+
therefore harder to catch: an author writing `ttl: 3600` on a formula field would
44+
believe results were cached for an hour, get no error, and never see a signal
45+
that nothing had happened.
46+
47+
**Migration.** There is no runtime behavior to migrate — neither schema was ever
48+
reachable from `FieldSchema`, and neither had a consumer in-tree. For per-field
49+
uniqueness use `unique` (`true` = unique within the tenant, `'global'` = unique
50+
platform-wide; see #3696). `completeness`, `accuracy`, and computed-field caching
51+
(`enabled` / `ttl` / `invalidateOn`) have no replacement; none was ever
52+
implemented.
53+
54+
If field-level data-quality governance or computed-field caching is built for
55+
real later, re-add the field key and its schema **together, with a consumer**
56+
the enforce side of enforce-or-remove (ADR-0049). A tombstone in `field.zod.ts`
57+
records this so neither schema is restored on its own again.

content/docs/references/data/field.mdx

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Field Type Enum
1414
## TypeScript Usage
1515

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

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

4040

41-
---
42-
43-
## ComputedFieldCache
44-
45-
### Properties
46-
47-
| Property | Type | Required | Description |
48-
| :--- | :--- | :--- | :--- |
49-
| **enabled** | `boolean` || Enable caching for computed field results |
50-
| **ttl** | `number` || Cache TTL in seconds (0 = no expiration) |
51-
| **invalidateOn** | `string[]` || Field paths that invalidate cache (e.g., ["inventory.quantity", "pricing.base_price"]) |
52-
53-
5441
---
5542

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

7865

79-
---
80-
81-
## DataQualityRules
82-
83-
### Properties
84-
85-
| Property | Type | Required | Description |
86-
| :--- | :--- | :--- | :--- |
87-
| **uniqueness** | `boolean` || Enforce unique values across all records |
88-
| **completeness** | `number` || Minimum ratio of non-null values (0-1, default: 0 = no requirement) |
89-
| **accuracy** | `{ source: string; threshold: number }` | optional | Accuracy validation configuration |
90-
91-
9266
---
9367

9468
## Field

packages/spec/api-surface.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,6 @@
192192
"ClockTimeValueSchema (const)",
193193
"ComparisonOperatorSchema (const)",
194194
"Compatibility (type)",
195-
"ComputedFieldCache (type)",
196-
"ComputedFieldCacheSchema (const)",
197195
"ConditionalValidation (type)",
198196
"ConditionalValidationSchema (const)",
199197
"ConsistencyLevel (type)",
@@ -252,9 +250,6 @@
252250
"DataEngineUpdateOptionsSchema (const)",
253251
"DataEngineUpdateRequestSchema (const)",
254252
"DataEngineVectorFindRequestSchema (const)",
255-
"DataQualityRules (type)",
256-
"DataQualityRulesInput (type)",
257-
"DataQualityRulesSchema (const)",
258253
"DataTypeMapping (type)",
259254
"DataTypeMappingSchema (const)",
260255
"Datasource (type)",

packages/spec/json-schema.manifest.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,6 @@
671671
"data/BaseEngineOptions",
672672
"data/CalendarDateValue",
673673
"data/ClockTimeValue",
674-
"data/ComputedFieldCache",
675674
"data/ConditionalValidation",
676675
"data/ConsistencyLevel",
677676
"data/ContextToken",
@@ -700,7 +699,6 @@
700699
"data/DataEngineUpdateOptions",
701700
"data/DataEngineUpdateRequest",
702701
"data/DataEngineVectorFindRequest",
703-
"data/DataQualityRules",
704702
"data/DataTypeMapping",
705703
"data/Datasource",
706704
"data/DatasourceCapabilities",

packages/spec/src/data/field.zod.ts

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -180,56 +180,6 @@ export const AddressSchema = lazySchema(() => z.object({
180180
formatted: z.string().optional().describe('Formatted address string'),
181181
}));
182182

183-
/**
184-
* Data Quality Rules Schema
185-
* Defines data quality validation and monitoring for fields
186-
*
187-
* @example Unique SSN field with completeness requirement
188-
* {
189-
* uniqueness: true,
190-
* completeness: 0.95, // 95% of records must have this field
191-
* accuracy: {
192-
* source: 'government_db',
193-
* threshold: 0.98
194-
* }
195-
* }
196-
*/
197-
export const DataQualityRulesSchema = lazySchema(() => z.object({
198-
/** Enforce uniqueness constraint */
199-
uniqueness: z.boolean().default(false).describe('Enforce unique values across all records'),
200-
201-
/** Completeness ratio (0-1) indicating minimum percentage of non-null values */
202-
completeness: z.number().min(0).max(1).default(0).describe('Minimum ratio of non-null values (0-1, default: 0 = no requirement)'),
203-
204-
/** Accuracy validation against authoritative source */
205-
accuracy: z.object({
206-
source: z.string().describe('Reference data source for validation (e.g., "api.verify.com", "master_data")'),
207-
threshold: z.number().min(0).max(1).describe('Minimum accuracy threshold (0-1, e.g., 0.95 = 95% match required)'),
208-
}).optional().describe('Accuracy validation configuration'),
209-
}));
210-
211-
/**
212-
* Computed Field Caching Schema
213-
* Configuration for caching computed/formula field results
214-
*
215-
* @example Cache product price with 1-hour TTL, invalidate on inventory changes
216-
* {
217-
* enabled: true,
218-
* ttl: 3600,
219-
* invalidateOn: ['inventory.quantity', 'pricing.discount']
220-
* }
221-
*/
222-
export const ComputedFieldCacheSchema = lazySchema(() => z.object({
223-
/** Enable caching for this computed field */
224-
enabled: z.boolean().describe('Enable caching for computed field results'),
225-
226-
/** Time-to-live in seconds */
227-
ttl: z.number().min(0).describe('Cache TTL in seconds (0 = no expiration)'),
228-
229-
/** Array of field paths that trigger cache invalidation when changed */
230-
invalidateOn: z.array(z.string()).describe('Field paths that invalidate cache (e.g., ["inventory.quantity", "pricing.base_price"])'),
231-
}));
232-
233183
/**
234184
* Field Schema - Best Practice Enterprise Pattern
235185
*/
@@ -544,6 +494,23 @@ export const FieldSchema = lazySchema(() => z.object({
544494
// the real channel is type:'secret'). See
545495
// docs/audits/2026-06-dead-surface-disposition-plan.md (P0/P2 field prune):
546496
// encryptionConfig, maskingRule, auditTrail, cached, dataQuality.
497+
//
498+
// Two of the five value schemas outlived their keys by a release — `dataQuality`'s
499+
// (`DataQualityRulesSchema` + the `DataQualityRules` / `DataQualityRulesInput`
500+
// types, #3726) and `cached`'s (`ComputedFieldCacheSchema` + `ComputedFieldCache`,
501+
// #3733). Each key was gone from this object while its schema stayed on the
502+
// published API surface and in the generated reference docs, so an author could
503+
// still discover the shape and write it. This object is NOT `.strict()`, so that
504+
// write did not fail loudly: it parsed clean and the key was silently stripped —
505+
// the same ADR-0104 failure class as the pre-declaration `accept` / `maxSize`
506+
// above (accepted in source, dropped in the contract, no feedback). Both schemas
507+
// are removed as of #3733; all five keys are now dead in both layers, as the
508+
// tombstone above always claimed.
509+
//
510+
// If field-level data-quality governance or computed-field caching is ever built
511+
// for real, re-add the key and its schema TOGETHER, with a consumer — the enforce
512+
// side of ADR-0049. Do not restore a schema on its own; that middle state is what
513+
// both issues were filed about.
547514

548515
/** Layout & Grouping */
549516
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<typeof AddressSchema>;
642609
export type CurrencyConfig = z.infer<typeof CurrencyConfigSchema>;
643610
export type CurrencyConfigInput = z.input<typeof CurrencyConfigSchema>;
644611
export type CurrencyValue = z.infer<typeof CurrencyValueSchema>;
645-
export type DataQualityRules = z.infer<typeof DataQualityRulesSchema>;
646-
export type DataQualityRulesInput = z.input<typeof DataQualityRulesSchema>;
647-
export type ComputedFieldCache = z.infer<typeof ComputedFieldCacheSchema>;
648612

649613
/**
650614
* Field Factory Helper

0 commit comments

Comments
 (0)