diff --git a/.changeset/17786-duration-describe-units.md b/.changeset/17786-duration-describe-units.md new file mode 100644 index 0000000000..9295e04201 --- /dev/null +++ b/.changeset/17786-duration-describe-units.md @@ -0,0 +1,18 @@ +--- +"@objectstack/spec": patch +--- + +Three duration keys now name their unit in the `.describe()` prose that reaches the published output, not only in the key name and the JSDoc above them: `PluginLoadingEvent.durationMs` (`kernel/plugin-loading.zod.ts`), `AppInstallResult.durationMs` (`system/app-install.zod.ts`) and `MigrationPlan.estimatedDurationMs` (`system/deploy-bundle.zod.ts`). + +The first carried no `.describe()` at all, so the generated reference row for `durationMs` rendered an empty description cell; the other two said `Installation duration` and `Estimated execution time`, naming a duration with no unit. All three JSDoc blocks already said milliseconds, and all three key names already carry `Ms`. Only the channel an author — very often a model (ADR-0033) — actually reads was missing it. + +⛔ Not a rename, and no key moves: the unit is already in the key name, which is what the #14478 rule asks for. This is the describe-only remediation of Ruling A on #15939, and it is the one of the seven remediations that needs no ADR-0087 conversion, no tombstone and no published-key rename. + +**The published surface was measured rather than assumed**, because a changeset is owed only if the changed text actually ships. Measured after `pnpm --filter @objectstack/spec build`, over the paths this package's `files[]` actually publishes: + +- **The changed text ships.** `Duration in milliseconds` reads 24 occurrences across 12 `dist/` bundle files and 6 across `json-schema/`; the other two read 8 in `dist/` and 2 in `json-schema/` each. The generated reference pages under `content/docs/references/**` render all three rows and are regenerated in this change. +- **Positive control that ships**: the neighbouring describe `Objects created/updated` — `dist` 4, `json-schema` 2. +- **Negative control that does not ship**: `no exemption by blindness`, a sentence that exists only in `packages/spec/scripts/`, a path outside `files[]` — 0 across every published path, 1 in its own unpublished file. +- **Dark control**: a fabricated needle reads 0 everywhere, so a zero above is a reading rather than a broken instrument. + +One measured refinement worth recording for the next author, since it cuts against the obvious reading of "published output": **`dist/` alone does not discriminate the two prose channels.** JSDoc text and even a `//` line comment ride into the emitted bundles verbatim (`Objects created or updated`, JSDoc-only, reads 4 in `dist/`). What separates the channels is `json-schema/`, which carries describe prose and 0 comment prose. So `dist` presence is necessary and not sufficient evidence that a string reached the governed channel; the `json-schema/` reading is the one that decides it. diff --git a/content/docs/references/kernel/plugin-loading.mdx b/content/docs/references/kernel/plugin-loading.mdx index 46ad18cfe0..abde1743ee 100644 --- a/content/docs/references/kernel/plugin-loading.mdx +++ b/content/docs/references/kernel/plugin-loading.mdx @@ -43,7 +43,7 @@ Plugin loading lifecycle event | **type** | `Enum<'load-started' \| 'load-completed' \| 'load-failed' \| 'init-started' \| 'init-completed' \| 'init-failed' \| 'preload-started' \| 'preload-completed' \| 'cache-hit' \| … +5 more>` | ✅ | | | **pluginId** | `string` | ✅ | | | **timestamp** | `integer` | ✅ | | -| **durationMs** | `integer` | optional | | +| **durationMs** | `integer` | optional | Duration in milliseconds | | **metadata** | `Record` | optional | | | **error** | `{ message: string; code?: string; stack?: string }` | optional | | diff --git a/content/docs/references/system/app-install.mdx b/content/docs/references/system/app-install.mdx index 223b78d61a..060f1c8df1 100644 --- a/content/docs/references/system/app-install.mdx +++ b/content/docs/references/system/app-install.mdx @@ -86,7 +86,7 @@ App install result | **installedObjects** | `string[]` | optional (default: `[]`) | Objects created/updated | | **createdTables** | `string[]` | optional (default: `[]`) | Database tables created | | **seededRecords** | `integer` | optional (default: `0`) | Seed records inserted | -| **durationMs** | `integer` | optional | Installation duration | +| **durationMs** | `integer` | optional | Installation duration in milliseconds | | **error** | `string` | optional | Error message on failure | diff --git a/content/docs/references/system/deploy-bundle.mdx b/content/docs/references/system/deploy-bundle.mdx index 493a796f51..0d9a395b8d 100644 --- a/content/docs/references/system/deploy-bundle.mdx +++ b/content/docs/references/system/deploy-bundle.mdx @@ -189,7 +189,7 @@ Ordered migration plan | **statements** | `{ sql: string; reversible: boolean; rollbackSql?: string; order: integer }[]` | optional (default: `[]`) | Ordered DDL statements | | **dialect** | `string` | ✅ | Target SQL dialect | | **reversible** | `boolean` | optional (default: `true`) | Whether the plan can be fully rolled back | -| **estimatedDurationMs** | `integer` | optional | Estimated execution time | +| **estimatedDurationMs** | `integer` | optional | Estimated execution time in milliseconds | ### Nested Shape: `MigrationPlan.statements[number]` diff --git a/packages/spec/src/kernel/plugin-loading.zod.ts b/packages/spec/src/kernel/plugin-loading.zod.ts index 418e7ba408..4810e97607 100644 --- a/packages/spec/src/kernel/plugin-loading.zod.ts +++ b/packages/spec/src/kernel/plugin-loading.zod.ts @@ -101,7 +101,7 @@ export const PluginLoadingEventSchema = lazySchema(() => z.object({ /** * Duration in milliseconds */ - durationMs: z.number().int().min(0).optional(), + durationMs: z.number().int().min(0).optional().describe('Duration in milliseconds'), /** * Additional metadata diff --git a/packages/spec/src/system/app-install.zod.ts b/packages/spec/src/system/app-install.zod.ts index 3ce3a1ca63..c668062769 100644 --- a/packages/spec/src/system/app-install.zod.ts +++ b/packages/spec/src/system/app-install.zod.ts @@ -143,7 +143,7 @@ export const AppInstallResultSchema = lazySchema(() => z.object({ seededRecords: z.number().int().min(0).default(0).describe('Seed records inserted'), /** Installation duration in milliseconds */ - durationMs: z.number().int().min(0).optional().describe('Installation duration'), + durationMs: z.number().int().min(0).optional().describe('Installation duration in milliseconds'), /** Error message if installation failed */ error: z.string().optional().describe('Error message on failure'), diff --git a/packages/spec/src/system/deploy-bundle.zod.ts b/packages/spec/src/system/deploy-bundle.zod.ts index 584eabe13f..e6bec69704 100644 --- a/packages/spec/src/system/deploy-bundle.zod.ts +++ b/packages/spec/src/system/deploy-bundle.zod.ts @@ -126,7 +126,7 @@ export const MigrationPlanSchema = lazySchema(() => z.object({ reversible: z.boolean().default(true).describe('Whether the plan can be fully rolled back'), /** Estimated execution time in milliseconds */ - estimatedDurationMs: z.number().int().min(0).optional().describe('Estimated execution time'), + estimatedDurationMs: z.number().int().min(0).optional().describe('Estimated execution time in milliseconds'), }).describe('Ordered migration plan')); export type MigrationPlan = z.input;