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
29 changes: 29 additions & 0 deletions .changeset/18124-genuine-duration-rows-declare-their-unit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@objectstack/spec': minor
---

spec: the genuine duration rows declare their unit — `DurationMs` / `DurationSeconds` and two `externalVocabulary` mirrors (#18124)

**BREAKING** — three keys that accepted any `number` now accept whole, non-negative numbers only. No key is renamed, added or removed, and no exported symbol moves.

Step ③ of ruling A on #18115. Step ① added the closed duration vocabulary and step ② taught `check:duration-unit-keys` to read it; this converts the rows the census found carrying a genuine duration with its unit written down in no channel a reader can reach.

**Six rows declare the unit on the value**, by adopting `DurationMs` / `DurationSeconds` (`@objectstack/spec/shared`) and stating the unit in the describe the reference page renders:

- `API.BaseResponse.meta.duration` — milliseconds
- `Kernel.HotReloadConfig.shutdownTimeout` — milliseconds
- `System.MetricAggregationConfig.window.slideInterval` — seconds
- `System.MetricsConfig.retention.downsampling[].resolution` — seconds
- `System.MetadataLoadResult.loadTime`, `System.MetadataSaveResult.saveTime` — milliseconds

**Two rows declare it by mirror**, with `.meta({ externalVocabulary })` plus the unit in the describe, because the key name is fixed outside this repo and renaming it would break the correspondence that makes it readable:

- `Kernel.KernelSecurityPolicy.cors.maxAge` — seconds, per CORS `Access-Control-Max-Age` (WHATWG Fetch). This is the same declaration its twin `CorsConfig.maxAge` already carried.
- `System.AuthConfig.session.updateAge` — seconds, per better-auth `session.updateAge`. Its sibling `session.expiresIn` already carried the marker; this closes the pair.

**What an author must change: nothing, unless they were writing a fraction or a negative span.** Only `meta.duration`, `loadTime` and `saveTime` change what they accept — each was a bare `z.number()` and is now `z.number().int().nonnegative()`. `shutdownTimeout` declared `.int().min(0)` and `slideInterval` / `resolution` declared `.int().positive()`; all three keep their floor, so their accepted set is byte-for-byte what it was and only their description is new. The two mirror rows keep their types untouched.

Every unit is a measurement of the row's producer, printed in the PR body per row, never a reading of the key name.

Clause-②: no (narrowing)
<!-- adr-0087: not-required (no-migration-prescription) No key is renamed, removed or retired, so there is no FROM to TO mapping an upgrader could be given: the three narrowing rows refuse only a fraction or a negative span, and every measured producer already writes a whole, non-negative count. -->
33 changes: 30 additions & 3 deletions content/docs/references/api/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const result = AnalyticsEndpoint.parse(data);
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; title?: string; measures: object[]; dimensions: object[] }[]` | ✅ | Available cubes, each as the `CubeMeta` discovery projection — the cube name, its title, and the measures/dimensions a client may name in a query. A bare array: there is no `cubes` wrapper object, and no cube `sql` is published. |

### Nested Shape: `AnalyticsMetadataResponse.error`
Expand All @@ -62,6 +62,15 @@ const result = AnalyticsEndpoint.parse(data);
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `AnalyticsMetadataResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |

### Nested Shape: `AnalyticsMetadataResponse.data[number]`

| Property | Type | Required | Description |
Expand Down Expand Up @@ -111,7 +120,7 @@ const result = AnalyticsEndpoint.parse(data);
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ rows: Record<string, any>[]; fields: object[]; sql?: string; totals?: object[] }` | ✅ | |

### Nested Shape: `AnalyticsResultResponse.error`
Expand All @@ -128,6 +137,15 @@ const result = AnalyticsEndpoint.parse(data);
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `AnalyticsResultResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |

### Nested Shape: `AnalyticsResultResponse.data`

| Property | Type | Required | Description |
Expand All @@ -148,7 +166,7 @@ const result = AnalyticsEndpoint.parse(data);
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ sql: string; params: any[] }` | ✅ | |

### Nested Shape: `AnalyticsSqlResponse.error`
Expand All @@ -165,6 +183,15 @@ const result = AnalyticsEndpoint.parse(data);
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `AnalyticsSqlResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |


---

Expand Down
22 changes: 20 additions & 2 deletions content/docs/references/api/auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const result = AuthProvider.parse(data);
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ session: object; user: object; token?: string }` | ✅ | |

### Nested Shape: `SessionResponse.error`
Expand All @@ -135,6 +135,15 @@ const result = AuthProvider.parse(data);
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `SessionResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |

### Nested Shape: `SessionResponse.data`

| Property | Type | Required | Description |
Expand Down Expand Up @@ -176,7 +185,7 @@ const result = AuthProvider.parse(data);
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ id: string; email: string; emailVerified: boolean; name: string; … }` | ✅ | |

### Nested Shape: `UserProfileResponse.error`
Expand All @@ -193,6 +202,15 @@ const result = AuthProvider.parse(data);
| **details** | `any` | optional | Additional error context (e.g. field validation errors) |
| **requestId** | `string` | optional | Request ID for tracking |

### Nested Shape: `UserProfileResponse.meta`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **timestamp** | `string` | ✅ | |
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
| **requestId** | `string` | optional | |
| **traceId** | `string` | optional | |

### Nested Shape: `UserProfileResponse.data`

| Property | Type | Required | Description |
Expand Down
Loading
Loading