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
18 changes: 18 additions & 0 deletions .changeset/17786-duration-describe-units.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion content/docs/references/kernel/plugin-loading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>` | optional | |
| **error** | `{ message: string; code?: string; stack?: string }` | optional | |

Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/system/app-install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |


Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/system/deploy-bundle.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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]`

Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/kernel/plugin-loading.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/system/app-install.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/system/deploy-bundle.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof MigrationPlanSchema>;
Expand Down
Loading