From 52d8b71c7b1c946a6299de5add653372fe982665 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:35:53 +0000 Subject: [PATCH 1/2] docs(spec): name the millisecond unit in three duration keys' describe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `durationMs` (kernel/plugin-loading.zod.ts, system/app-install.zod.ts) and `estimatedDurationMs` (system/deploy-bundle.zod.ts) already carry their unit in the key name and in the JSDoc above them, but the `.describe()` prose — the channel that reaches the published reference pages and the published dist — named no unit at all, and plugin-loading's key carried no describe whatsoever. Prose only: no key added, no export moved, no accept/reject outcome changed. Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt Co-authored-by: Claude --- packages/spec/src/kernel/plugin-loading.zod.ts | 2 +- packages/spec/src/system/app-install.zod.ts | 2 +- packages/spec/src/system/deploy-bundle.zod.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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; From eac52ecb7b94d18cf0a16d52f43132fc3bf19ed3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 07:43:28 +0000 Subject: [PATCH 2/2] docs(spec): regenerate the three reference rows and record the published-surface measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pnpm --filter @objectstack/spec check:generated` proved exactly one artifact stale — `content/docs/references/**` — and it was regenerated with the repo's own generator (`gen:docs`), never hand-edited. The three rows that moved are the three describe strings this change edits. The changeset records the measurement the card asked for: the changed describe text reaches the published `dist/` bundles and `json-schema/`, with a positive control that ships, a negative control outside `files[]` that does not, and a dark control at zero. Claude-Session: https://claude.ai/code/session_015c5G6TmpMKgnusmTpD7Ntt Co-authored-by: Claude --- .changeset/17786-duration-describe-units.md | 18 ++++++++++++++++++ .../docs/references/kernel/plugin-loading.mdx | 2 +- content/docs/references/system/app-install.mdx | 2 +- .../docs/references/system/deploy-bundle.mdx | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 .changeset/17786-duration-describe-units.md 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]`