Skip to content

Commit c049e74

Browse files
os-billclaude
andauthored
spec: the genuine duration rows declare their unit — DurationMs/DurationSeconds and two externalVocabulary mirrors (#18657)
Part of #18124 Clause-②: no Step ③ of ruling A on #18115 (decision batch #134 item 1). Eight of the census's ten genuine-duration rows now declare their unit through a channel a reader actually reaches. Two rows are reported back to the director seat instead of converted, each with the measurement that stopped it. ⚠️ `Part of`, not a closing keyword, and deliberately: two ruled rows are not delivered here, so a merge must not retire the card. The director seat decides when it closes. The two are named under "Reported back" below. ## The blocker cleared — proved by subject probe, not by card state Card #18123 (step ②) is open, but its SUBJECT is on `main`. Commit `8ca7aafc45` — *spec(gate): check-duration-unit-keys admits by declaration — retire the name-shape token list*, PR #18486 — carries `Part of #18123` rather than a closing keyword, so the behaviour shipped while the card stayed open. On this branch's merge base `922c75588`, `DURATION_ROOTS` in `packages/spec/scripts/check-duration-unit-keys.ts` already maps both `DurationMs` and `DurationSeconds`. That answers the dispatch's open question — *does the gate stop seeing a row that adopts `DurationMs` while its key name carries no unit, or start refusing it?* — with a third answer: **neither**. | | before | after | |:--|:--|:--| | all ten ruled rows in `--list` | absent — each declares nothing, so none is admitted to the census | the eight converted rows are admitted through the channel each adopted | | offenders | 0 | 0 | | declared duration types | 0 | **6** — equals the rows converted by type, which is the card's own acceptance line | | declared `externalVocabulary` mirrors | 11 | **13** | | declared `EpochMs` instants | 15 | 15 | | declared `dimensionless` | 0 | 0 | | census size | 195 | 203 | A converted row is admitted by its TYPE (or by its mirror marker) and exempted from the key-NAME requirement; both contradiction directions stay refusable. Nothing is refused that was not refused before. ## The population, re-derived by key + file, with the unit MEASURED per row The card's "likely unit" column carries its own warning and was not copied. Each row's producer was measured; where the measurement came back EMPTY that is stated as such rather than dressed up. | file · key | measured unit | what was measured, and what it returned | route | |:--|:--|:--|:--| | `api/contract.zod.ts` `BaseResponse.meta.duration` | ms | **No producer exists.** `packages/rest/src` contains no `timestamp:` writer at all, so the envelope's `meta` block is never emitted; a repo-wide sweep of `duration:` assignments in non-test sources returns only `Date.now() - start` sites (`cli/src/utils/config.ts`, `core/src/qa/runner.ts`, `timer.elapsed()` = `Date.now() - start`). Declared from the sibling channel instead: every spec key that spells a processing time spells ms (`tracing.durationMs`, `worker.durationMs`, `worker.avgExecutionMs`), and the schema's own test literal is `150`. | `DurationMs` | | `kernel/plugin-lifecycle-advanced.zod.ts` `HotReloadConfig.shutdownTimeout` | ms | `packages/core/src/hot-reload.ts` is the only in-repo reader and treats it as a millisecond budget; its suite drives `120_000`, `1000` and `50` through it. The #15676 wave recorded in the migration ledger that this sibling is "deliberately NOT renamed" with `debounceDelayMs`, so the type route is the one the repo already chose. | `DurationMs` | | `kernel/plugin-security-advanced.zod.ts` `KernelSecurityPolicy.cors.maxAge` | seconds | This key IS the CORS `Access-Control-Max-Age` header. Its TWIN — `shared/http.zod.ts` `CorsConfig.maxAge` — is the same field and already declares `.meta({ externalVocabulary: 'CORS ...' })` with the describe "Preflight cache duration in seconds". The live emitter, `packages/adapters/hono/src/index.ts`, reads `OS_CORS_MAX_AGE` with a default of `86400` and hands it to the header. | `externalVocabulary` | | `system/auth-config.zod.ts` `AuthConfig.session.updateAge` | seconds | `packages/plugins/plugin-auth/src/auth-manager.ts` forwards it by name: `updateAge: this.config.session?.updateAge || 60 * 60 * 24` straight into better-auth's `session.updateAge`, seconds. The comment above the pair in the schema already calls BOTH names better-auth's "forwarded by name" — only `expiresIn` carried the marker. | `externalVocabulary` | | `system/metrics.zod.ts` `MetricAggregationConfig.window.slideInterval` | seconds | No runtime reader. The sibling it slides across in the same object literal is `durationSeconds`, and the schema's own fixture pairs `durationSeconds: 300` with `slideInterval: 60`. | `DurationSeconds` | | `system/metrics.zod.ts` `MetricsConfig.retention.downsampling[].resolution` (class B) | seconds | No runtime reader. Its JSDoc says "Resolution in seconds" and its sibling in the same object is `afterSeconds`. The unit was in the JSDoc alone — the #14519 shape, invisible on the published page. | `DurationSeconds` | | `system/metadata-persistence.zod.ts` `MetadataLoadResult.loadTime` (class D) | ms | **Every** producer agrees: `metadata/src/loaders/filesystem-loader.ts` and `database-loader.ts` write `Date.now() - startTime`; `memory-loader.ts` and `remote-loader.ts` write literal `0`. | `DurationMs` | | `system/metadata-persistence.zod.ts` `MetadataSaveResult.saveTime` (class D) | ms | Same producers, same expression. | `DurationMs` | Class B's second row and one class A-true row are **not** converted — see below. ### Why two rows took `externalVocabulary` and not a type The dispatch left this to measurement. For `cors.maxAge` the decisive reading is that an identical key already exists in this spec and already carries the marker; giving the twin a different declaration would make one field read two ways. For `updateAge` the decisive reading is the schema's own comment, which describes the `expiresIn` / `updateAge` PAIR as better-auth names forwarded verbatim while only one of the two carried the marker. Neither route changes the accepted set, so this does not move the declaration: `Clause-②: no` stands either way. ## Reported back rather than converted — ⛔ both are correct outcomes, not gaps **1. `data/field-value.zod.ts` `FileValue.duration` — the unit is not measurable, and both closed types are hazardous here.** - Producer: **none**. There is no media-metadata writer in this repo — no `ffprobe`, no probe of any kind; `ObjectQLEngine.resolveFileReferences` expands a file reference to `{ id, name, size, mimeType, url }` and never writes `duration`. - Consumer: **none** in this repo, and none in the sibling objectui checkout available here (its only `duration` hits are Tailwind transition classes). - Documentation: `content/docs/protocol/objectql/types.mdx` names `duration` as an optional member and states no unit. - The external convention a producer would follow — `HTMLMediaElement.duration`, `ffprobe` — is **fractional seconds**. `DurationSeconds` is `.int()`, so adopting it would refuse `12.34`; `DurationMs` would contradict that convention by a factor of 1000. - And this key is reachable by STORED data: `FileValueSchema` is the pre-v17 inline blob that ADR-0104's dual-mode window keeps parsing, so `.int()` narrows against rows that may already exist — a different risk class from the rest of this tranche. Three ways forward, all outside this card's fences: accept integral seconds and its truncation; add a fractional-seconds unit to the vocabulary (⛔ "no new type"); or rename (⛔ the rename branch). The director seat sequences it. **2. `kernel/plugin-versioning.zod.ts` `CompatibilityMatrixEntry.estimatedMigrationTime` — the unit is HOURS, which no type in the closed vocabulary covers.** Its JSDoc reads "Estimated migration time in hours" and the schema's own fixtures are `8` and `40`. There is no runtime producer or consumer. `DurationMs` / `DurationSeconds` cannot carry hours, the card fences a new type, and the remaining route — moving the unit into the describe — lands the row straight on `unit-in-prose-not-in-name`, whose prescription is a RENAME of a published authorable key (`kernel/CompatibilityMatrixEntry:estimatedMigrationTime` is on the authorable surface). That owes an ADR-0087 entry in `packages/spec/src/migrations/registry.ts`, a file held by seat 1's in-flight card. ⛔ Not opened. ## Red before green **The gate that reverse-requires this change is `check:docs`** (with `check:skill-refs` beside it). Before regeneration, on the converted sources: ``` ✗ 2 of 15 artifact(s) stale: skill references pnpm --filter @objectstack/spec gen:skill-refs content/docs/references/** pnpm --filter @objectstack/spec gen:docs ``` After `check:generated --fix`: `✓ All 15 generated artifacts are up to date.` **`check:duration-unit-keys` itself cannot go red for an unconverted row**, and that is by design, not an oversight: a key that declares nothing is not admitted to the census, so there is no verdict to fail. What CAN go red is the declaration once it exists — both routes, proved by ablation from the committed state, each leg with an on-disk proof and a hash-verified restore: ``` LEG A mutate slideInterval's describe: seconds -> milliseconds ON-DISK PROOF: deleted-text count 1 -> 0; injected-text count = 1 MUTATED_EXIT=1 [duration-unit-contradicts-schema] .../metrics.zod.ts:416 `slideInterval` — typed `DurationSeconds` (seconds) but the describe says ms. restore proof: disk=2063c360949dc2a35e7fa88501f23ec7cf76970e head=2063c36... RESTORED_EXIT=0 LEG B delete updateAge's externalVocabulary marker ON-DISK PROOF: marker count 1 -> 0 PIN_MUTATED_EXIT=1 (the new pin fails: externalVocabulary marker absent) GATE_WITH_MARKER_REMOVED_EXIT=1 [unit-in-prose-not-in-name] .../auth-config.zod.ts:563 `updateAge` — describe names seconds but the key name carries no unit. restore proof: disk=e1fd6e449e6629f5c848f7ff8bd77d3d5aa6132b head=e1fd6e4... PIN_RESTORED_EXIT=0 (48 passed) ``` Leg B is the stronger reading of the two: it shows the mirror route is *also* load-bearing, which this author expected NOT to be the case. Removing the marker does not make the key vanish from the census, because the describe still names the unit — so the gate refuses it. ## Controls ⭐ **LIT** — already-declared siblings, unchanged and still reading as declared in `--list` after: ``` shared/http.zod.ts:143 maxAge [externalVocabulary: CORS `Access-Control-Max-Age` (WHATWG Fetch)] api/http-cache.zod.ts:82 maxAge [externalVocabulary: HTTP Cache-Control `max-age` ...] metrics.zod.ts:400 durationSeconds [name: seconds] [prose: seconds] (slideInterval's own sibling) auth-config.zod.ts:561 expiresIn [externalVocabulary: better-auth `session.expiresIn`] (updateAge's own sibling) metrics.zod.ts:847 afterSeconds [name: seconds] [prose: seconds] (resolution's own sibling) ``` ⭐ **DARK** — things that must read zero, and do: - `EpochMs` instants 15 → 15 and `dimensionless` 0 → 0: the census's deliberately-excluded classes (the 10 instants, the 17 dimensionless rows) did not move. - The two rows reported back stayed absent from the census: 0 rows outside the ruled population changed declaration. - The census grew by exactly 8 (195 → 203), the number of rows converted, with no other row entering or leaving. - `check:authorable-surface` green with no regeneration: not one authorable key was added, removed or renamed. ## Verification | command | verdict | |:--|:--| | `pnpm --filter @objectstack/spec build` | exit 0 | | `pnpm --filter @objectstack/spec check:generated` | exit 0 — ✓ All 15 generated artifacts are up to date. | | `pnpm --filter @objectstack/spec check:authorable-surface` | exit 0 | | `pnpm --filter @objectstack/spec check:api-surface` | exit 0 | | `pnpm --filter @objectstack/spec check:docs` | exit 0 | | `pnpm --filter @objectstack/spec typecheck` | exit 0 (`tsc --noEmit` + scripts + test layer) | | `pnpm --filter @objectstack/spec test` | exit 0 — 484 files passed, 1 skipped; **13864 tests passed** | | `tsx scripts/check-duration-unit-keys.ts --list` before / after | exit 0 / exit 0 — table above | | `pnpm --filter @objectstack/spec check:duration-unit-keys` | exit 0 — zero offenders, no baseline | | `node scripts/pm/check-widening-tells.mjs --declaration no --diff ...` | exit 0 — 29 files, 6 judged against a declared surface, no widening tell | | `node scripts/check-adr-0087-registration.mjs --base origin/main` | exit 0 — 1 declared-breaking changeset, disposition `not-required (no-migration-prescription)` | | `pnpm check:nul-bytes` | exit 0 | | 20 further derived gates (skill-refs, exported-any, dual-source-exports, export-origins, liveness, empty-state, variant-docs, yaml-examples, strictness-ledger, doc-frontmatter, docs-single-h1, doc-anchors, spec-docblock-symbol-anchors, pm-widening-tells, watch-hint-literal, merge-driver, spec-parsed-alias, docs-spec-enumerations, quick-reference-counts, published-files, type-source-resolution, skills-token-ratchet, changeset-fixed, empty-changeset, changeset-no-major, test-source-alias) | exit 0 | **NOT MEASURED, stated as such:** - `pnpm --filter @objectstack/spec check:skill-examples` — refuses to run without `@objectstack/client-react` built (a prerequisite this branch did not build). Not a red on this diff; CI builds it. - `dispatch-gates.mjs --ran` reconciles **33 of 110** derived families. The remaining 77 are the farm, and are CI's — this is a declared narrowing, not a silent one. - `pnpm check:cross-package-test-inputs` exits 1 on this tree, and **it is not this diff**: the finding is that `packages/cli/test/init-created-files-summary.e2e.test.ts` descends `packages/spec/dist/` with no declared glob reaching inside it. None of this branch's 29 paths touches `packages/cli/**`, the declaration table, or that gate's script — and that script is the subject of a separate in-flight PR. ## Skill-surface readings (this diff touches a published skill path) The only `skills/**` change is one generated line in `skills/objectstack-api/references/_index.md`, written by `gen:skill-refs` because `shared/duration.zod.ts` now has an importer inside the API skill's reachable set. - changed file, before → after: **50 → 51 lines** (+1, generated) - whole published package, sum of every `SKILL.md`, before → after: **6145 → 6145 lines** (+0) - `node scripts/check-skills-token-ratchet.mjs`: exit 0 ⚠️ **PM 更正(实测推翻本段原文;逐条读数见评论 `5714917300`)。** 原文写「`skills/**` 是受管面,所以本 PR 不走合并队列、等维护者点头」。`node scripts/pm/check-governed-merges.mjs --pr 18657` 在**装了依赖的 checkout** 上读回 `✅ NOT governed — ordinary queue landing applies`:该行被 **#11705 生成物例外**(维护者 2026-08-25 取 A)按**字节**抬起 —— 它与 `pnpm --filter @objectstack/spec gen:skill-refs` 在本树现算的输出完全相等,且本 PR 的 29 条路径一条都没碰 `packages/spec/scripts/`(#11084 共编栅栏未触发)。CI 的 `Governed Surface Queue Guard` 同向读 success。⇒ **本 PR 走普通队列落地,⛔ 不等人批。** ## Acceptance notes - `MetricsConfig.retention.downsampling[].resolution` gains its unit in the JSON Schema (`json-schema/system/MetricsConfig.json`) but **not** on the rendered reference page: `build-docs.ts` emits a nested table for an object-valued member (which is why `KernelSecurityPolicy.cors` gains one here) and does not for an ARRAY-of-object member, so `downsampling` stays a one-line type signature. The declaration is correct and reaches the JSON Schema; the page is one generator behaviour short of showing it. Noted, not filed. - `RestServerConfig.responseFormat.includeMetadata` and `PluginRestApiConfig.includeMetadata` both declare "include response metadata (timestamp, requestId)", and nothing in `packages/rest/src` writes that block — the same absence that made `meta.duration` unmeasurable. Declared, unimplemented. Noted, not filed. ## File surface Every path this branch touches: ``` packages/spec/src/api/contract.zod.ts converted row + import packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts converted row + import packages/spec/src/kernel/plugin-security-advanced.zod.ts converted row (mirror) packages/spec/src/system/auth-config.zod.ts converted row (mirror) packages/spec/src/system/metadata-persistence.zod.ts two converted rows + import packages/spec/src/system/metrics.zod.ts two converted rows + import packages/spec/src/api/contract.test.ts pins packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts pins packages/spec/src/kernel/plugin-security-advanced.test.ts pins packages/spec/src/system/auth-config.test.ts pins packages/spec/src/system/metadata-persistence.test.ts pins packages/spec/src/system/metrics.test.ts pins .changeset/18124-genuine-duration-rows-declare-their-unit.md content/docs/references/api/{analytics,auth,automation-api,batch,contract,export,metadata,package-api,protocol,storage}.mdx generated content/docs/references/kernel/{plugin-lifecycle-advanced,plugin-security-advanced}.mdx generated content/docs/references/system/{auth-config,metadata-persistence,metrics}.mdx generated skills/objectstack-api/references/_index.md generated · #11705 例外实测抬起(NOT governed) ``` From the fenced OPEN set: **nothing**. `packages/spec/scripts/check-duration-unit-keys.ts`, `packages/spec/src/migrations/**`, `packages/spec/src/contracts/automation-service.ts`, `packages/services/service-automation/**`, `packages/types/src/env.ts`, `content/docs/automation/flows.mdx` and `scripts/check-cross-package-test-inputs.mjs` are all untouched. `packages/spec/src/api/contract.zod.ts` IS touched and is the file that is mine; the similarly-named `contracts/automation-service.ts` is not. ## 维护者速读(草稿) **改了什么。** 规格里 8 个「真时长」字段现在把自己的单位写在了合约上:6 个改用闭合类型 `DurationMs` / `DurationSeconds`,2 个挂 `externalVocabulary` 标记(它们的键名来自外部标准,改名会切断与标准的一一对应)。键名一个都没改,也没有增删任何可写的键。 **为什么改。** 一个 `duration: number` 字段,单位只写在代码注释里、或者哪里都没写,作者(很多时候是 AI)照着邻居的数字抄一个过来,就是一次静默的 ×1000 错误——缓存从 1 小时变成 3.6 秒,谁也不会收到报错。这一批把单位放进类型和发布出去的 JSON Schema / 参考页,让读文档的人和写元数据的人看到同一个答案。 **风险与代价(含回滚)。** 三个原本写作 `z.number()` 的字段现在只接受非负整数(`meta.duration`、`loadTime`、`saveTime`)。本仓库里测到的每一个写入方都已经在写整数,所以实测风险为零;真有人写小数,会在写的那一刻响亮报错,而不是悄悄算错。回滚就是 revert 本 PR,没有数据迁移、没有台账条目、没有墓碑。⚠️ ~~本 PR 动到 `skills/**`,属受管面,不走合并队列~~ —— **此句已被实测推翻**(评论 `5714917300`):那一行是生成器自己 `--check` 按字节认证的纯重生成,受 #11705 例外,**照常走合并队列**。 **席位意见。**(留空) **你要做的。** ① 确认两个「退回」的行由谁排期:`FileValue.duration` 的单位在本仓库测不出来(没有任何生产者),且两个闭合类型都有风险;`estimatedMigrationTime` 的单位是「小时」,现有类型覆盖不到,而改名会动到被别人占用的台账文件。② ~~确认 `skills/**` 那一行生成内容可以随本 PR 一起进~~ —— **这一条不用你答了**:仪器已答(评论 `5714917300`),按 #11705 例外自动随本 PR 进。⇒ **只剩 ① 需要你的字。** --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 30be2ac commit c049e74

29 files changed

Lines changed: 955 additions & 77 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
spec: the genuine duration rows declare their unit — `DurationMs` / `DurationSeconds` and two `externalVocabulary` mirrors (#18124)
6+
7+
**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.
8+
9+
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.
10+
11+
**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:
12+
13+
- `API.BaseResponse.meta.duration` — milliseconds
14+
- `Kernel.HotReloadConfig.shutdownTimeout` — milliseconds
15+
- `System.MetricAggregationConfig.window.slideInterval` — seconds
16+
- `System.MetricsConfig.retention.downsampling[].resolution` — seconds
17+
- `System.MetadataLoadResult.loadTime`, `System.MetadataSaveResult.saveTime` — milliseconds
18+
19+
**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:
20+
21+
- `Kernel.KernelSecurityPolicy.cors.maxAge` — seconds, per CORS `Access-Control-Max-Age` (WHATWG Fetch). This is the same declaration its twin `CorsConfig.maxAge` already carried.
22+
- `System.AuthConfig.session.updateAge` — seconds, per better-auth `session.updateAge`. Its sibling `session.expiresIn` already carried the marker; this closes the pair.
23+
24+
**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.
25+
26+
Every unit is a measurement of the row's producer, printed in the PR body per row, never a reading of the key name.
27+
28+
Clause-②: no (narrowing)
29+
<!-- 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. -->

‎content/docs/references/api/analytics.mdx‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const result = AnalyticsEndpoint.parse(data);
4545
| :--- | :--- | :--- | :--- |
4646
| **success** | `boolean` | ✅ | Operation success status |
4747
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
48-
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
48+
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
4949
| **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. |
5050

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

65+
### Nested Shape: `AnalyticsMetadataResponse.meta`
66+
67+
| Property | Type | Required | Description |
68+
| :--- | :--- | :--- | :--- |
69+
| **timestamp** | `string` | ✅ | |
70+
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
71+
| **requestId** | `string` | optional | |
72+
| **traceId** | `string` | optional | |
73+
6574
### Nested Shape: `AnalyticsMetadataResponse.data[number]`
6675

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

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

140+
### Nested Shape: `AnalyticsResultResponse.meta`
141+
142+
| Property | Type | Required | Description |
143+
| :--- | :--- | :--- | :--- |
144+
| **timestamp** | `string` | ✅ | |
145+
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
146+
| **requestId** | `string` | optional | |
147+
| **traceId** | `string` | optional | |
148+
131149
### Nested Shape: `AnalyticsResultResponse.data`
132150

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

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

186+
### Nested Shape: `AnalyticsSqlResponse.meta`
187+
188+
| Property | Type | Required | Description |
189+
| :--- | :--- | :--- | :--- |
190+
| **timestamp** | `string` | ✅ | |
191+
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
192+
| **requestId** | `string` | optional | |
193+
| **traceId** | `string` | optional | |
194+
168195

169196
---
170197

‎content/docs/references/api/auth.mdx‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const result = AuthProvider.parse(data);
118118
| :--- | :--- | :--- | :--- |
119119
| **success** | `boolean` | ✅ | Operation success status |
120120
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
121-
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
121+
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
122122
| **data** | `{ session: object; user: object; token?: string }` | ✅ | |
123123

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

138+
### Nested Shape: `SessionResponse.meta`
139+
140+
| Property | Type | Required | Description |
141+
| :--- | :--- | :--- | :--- |
142+
| **timestamp** | `string` | ✅ | |
143+
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
144+
| **requestId** | `string` | optional | |
145+
| **traceId** | `string` | optional | |
146+
138147
### Nested Shape: `SessionResponse.data`
139148

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

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

205+
### Nested Shape: `UserProfileResponse.meta`
206+
207+
| Property | Type | Required | Description |
208+
| :--- | :--- | :--- | :--- |
209+
| **timestamp** | `string` | ✅ | |
210+
| **duration** | `integer` | optional | Server-side processing duration in milliseconds |
211+
| **requestId** | `string` | optional | |
212+
| **traceId** | `string` | optional | |
213+
196214
### Nested Shape: `UserProfileResponse.data`
197215

198216
| Property | Type | Required | Description |

0 commit comments

Comments
 (0)