Skip to content

Commit 4458bcd

Browse files
committed
fix(core): spell the plugin startup elapsed time durationMs, the unit-bearing name its spec contract declares
Discharges contract review 5555409410 on PR #16057 (head 4d20aa7): the new published member was spelled `duration`, a key `packages/spec` has since retired. Measured on origin/main f377394: `packages/spec/src/kernel/startup-orchestrator.zod.ts:173` declares `durationMs`; `:176` tombstones `duration` with `retiredKey()` ("Rename the key to `durationMs`"); the gate `packages/spec/scripts/check-duration-unit-keys.ts` is in the tree (landed e9fcd6b). Two maintainer rulings make the rule govern every runtime-emitted duration, and a per-plugin startup elapsed time on a public result type is one. - `PluginStartupResult.durationMs?: number` replaces the never-released `duration?: number`; both emit sites in `kernel.ts` follow; `startTime` keeps its deprecated-alias treatment exactly as before. - The provenance sentence in the interface JSDoc (which ships in the published `dist/index.d.ts`), in `kernel.ts`'s `{@link}` and in the changeset cited the retired `PluginStartupResultSchema.duration`; all three now cite `durationMs` and say the bare spelling is retired. - The two pin tests read `.durationMs`; their ceiling assertion is unchanged. No ADR-0087 treatment on the core side: the spec tombstone entry (`packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginStartupResult__duration.ts`) records that core's interface is a different type and not a reader of the schema; core simply does not adopt the retired spelling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
1 parent 4d20aa7 commit 4458bcd

4 files changed

Lines changed: 24 additions & 23 deletions

File tree

.changeset/core-plugin-startup-duration-name.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
"@objectstack/core": minor
33
---
44

5-
Plugin startup elapsed time is now reported as `duration` — the name the spec contract for the same result already declares. `startTime`, which never held a start time, is deprecated and still populated.
5+
Plugin startup elapsed time is now reported as `durationMs` — the unit-bearing name the spec contract for the same result declares. `startTime`, which never held a start time, is deprecated and still populated.
66

77
`PluginStartupResult.startTime` (`packages/core/src/plugin-loader.ts`) has always been assigned `Date.now() - startTime`, an elapsed duration, on both the success and the failure path. The name therefore asserts the opposite of the value: a reader who correctly takes `startTime` for an instant and writes `Date.now() - result.startTime` gets an age near the epoch rather than a wait. That is the one failure mode a unit convention cannot rescue — an ambiguous name makes someone stop and check, this one lets them proceed confidently wrong.
88

9-
This is not a naming preference but a divergence between what is declared and what is enforced. `packages/spec/src/kernel/startup-orchestrator.zod.ts` declares `duration: z.number().min(0)` — "Time taken to start the plugin in milliseconds" — for the same measure on the same result, the outcome of starting one plugin. The contract surface was already correct and `packages/core` had drifted away from it. The right spelling is also twelve lines above the defect in the same file: `PluginLoadResult.loadTime` carries the identical `Date.now() - startTime` computation under a name that does not lie.
9+
This is not a naming preference but a divergence between what is declared and what is enforced. `packages/spec/src/kernel/startup-orchestrator.zod.ts` declares `durationMs: z.number().min(0)` — "Time taken to start the plugin in milliseconds" — for the same measure on the same result, the outcome of starting one plugin; the bare `duration` spelling is retired there with a `retiredKey()` tombstone whose prescription is "Rename the key to `durationMs`", because a duration-shaped number carries its unit in its key name, never only in describe prose. The contract surface was already correct and `packages/core` had drifted away from it. The same computation already has an honest name twelve lines above the defect in the same file: `PluginLoadResult.loadTime` carries the identical `Date.now() - startTime` under a name that does not lie.
1010

1111
Three sites move, and every one of them is additive — nothing is removed, so no consumer has to change anything on this release:
1212

13-
- `PluginStartupResult` gains `duration?: number`. `startTime?: number` stays, still carrying the same value, marked `@deprecated` with a doc comment that states plainly it is elapsed milliseconds and not an instant.
13+
- `PluginStartupResult` gains `durationMs?: number`. `startTime?: number` stays, still carrying the same value, marked `@deprecated` with a doc comment that states plainly it is elapsed milliseconds and not an instant.
1414
- `ObjectKernel.getPluginStartupDurations()` is added; `getPluginMetrics()` becomes a `@deprecated` delegating alias returning the same map.
1515
- The private `pluginStartTimes` map is renamed `pluginStartupDurations` (private; no reader outside `kernel.ts` in this repo or in the pinned `objectui` sibling).
1616

17-
Migration, where you want it: read `result.duration` where you read `result.startTime`, and `kernel.getPluginStartupDurations()` where you called `kernel.getPluginMetrics()`. The values are identical, so the change can be made at leisure; both old spellings keep working until they are removed.
17+
Migration, where you want it: read `result.durationMs` where you read `result.startTime`, and `kernel.getPluginStartupDurations()` where you called `kernel.getPluginMetrics()`. The values are identical, so the change can be made at leisure; both old spellings keep working until they are removed.
1818

19-
ADR-0087 disposition: no migration-ledger entry, and none is required. Nothing is retired by this release — the old member and the old method both remain, populated and callable, which is ADR-0087's L1 outcome (the old shape keeps loading while the fleet moves) rather than a retirement. There is also nothing for `objectstack migrate meta` to rewrite: `packages/core/src/plugin-loader.ts#PluginStartupResult` is a runtime TypeScript interface with no Zod schema, no `packages/spec` declaration and no stored representation — the `PluginStartupResult` in `packages/spec/src/kernel/startup-orchestrator.zod.ts` is a separate, differently-shaped declaration that this change does not touch. When the deprecated spellings are removed, that removal is the change that carries the ledger disposition.
19+
ADR-0087 disposition: no migration-ledger entry, and none is required. Nothing is retired by this release — the old member and the old method both remain, populated and callable, which is ADR-0087's L1 outcome (the old shape keeps loading while the fleet moves) rather than a retirement. There is also nothing for `objectstack migrate meta` to rewrite: `packages/core/src/plugin-loader.ts#PluginStartupResult` is a runtime TypeScript interface with no Zod schema, no `packages/spec` declaration and no stored representation — the `PluginStartupResult` in `packages/spec/src/kernel/startup-orchestrator.zod.ts` is a separate, differently-shaped declaration that this change does not touch, and that schema's own `duration` tombstone entry (`packages/spec/src/migrations/entries/retired-keys/18.kernel__PluginStartupResult__duration.ts`) records that core's interface is not a reader of it. Core simply does not adopt the retired spelling. When the deprecated spellings are removed, that removal is the change that carries the ledger disposition.

packages/core/src/kernel.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ describe('ObjectKernel', () => {
617617
await kernel.shutdown();
618618
});
619619

620-
it('PluginStartupResult.duration is an elapsed duration on both the success and the failure path', async () => {
620+
it('PluginStartupResult.durationMs is an elapsed duration on both the success and the failure path', async () => {
621621
const callStart = (meta: PluginMetadata): Promise<PluginStartupResult> =>
622622
(kernel as unknown as {
623623
startPluginWithTimeout(p: PluginMetadata): Promise<PluginStartupResult>;
@@ -634,10 +634,10 @@ describe('ObjectKernel', () => {
634634
const ok = await callStart(okMeta);
635635

636636
expect(ok.success).toBe(true);
637-
expect(ok.duration).toBeGreaterThan(0);
638-
expect(ok.duration).toBeLessThan(INSTANT_FLOOR_MS);
637+
expect(ok.durationMs).toBeGreaterThan(0);
638+
expect(ok.durationMs).toBeLessThan(INSTANT_FLOOR_MS);
639639
// The deprecated alias carries the same elapsed value, not an instant.
640-
expect(ok.startTime).toBe(ok.duration);
640+
expect(ok.startTime).toBe(ok.durationMs);
641641

642642
const failingMeta: PluginMetadata = {
643643
name: 'failing-plugin',
@@ -650,9 +650,9 @@ describe('ObjectKernel', () => {
650650
const failed = await callStart(failingMeta);
651651

652652
expect(failed.success).toBe(false);
653-
expect(failed.duration).toBeGreaterThanOrEqual(0);
654-
expect(failed.duration).toBeLessThan(INSTANT_FLOOR_MS);
655-
expect(failed.startTime).toBe(failed.duration);
653+
expect(failed.durationMs).toBeGreaterThanOrEqual(0);
654+
expect(failed.durationMs).toBeLessThan(INSTANT_FLOOR_MS);
655+
expect(failed.startTime).toBe(failed.durationMs);
656656
});
657657
});
658658

packages/core/src/kernel.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ export class ObjectKernel {
541541
/**
542542
* Per-plugin startup durations: plugin name -> elapsed milliseconds that
543543
* plugin's `start()` took. Not start instants -- see
544-
* {@link PluginStartupResult.duration}.
544+
* {@link PluginStartupResult.durationMs}.
545545
*/
546546
getPluginStartupDurations(): Map<string, number> {
547547
return new Map(this.pluginStartupDurations);
@@ -709,7 +709,7 @@ export class ObjectKernel {
709709
return {
710710
success: true,
711711
pluginName: plugin.name,
712-
duration,
712+
durationMs: duration,
713713
// Deprecated alias carrying the same elapsed value; see
714714
// PluginStartupResult.startTime.
715715
startTime: duration,
@@ -722,7 +722,7 @@ export class ObjectKernel {
722722
success: false,
723723
pluginName: plugin.name,
724724
error: error as Error,
725-
duration,
725+
durationMs: duration,
726726
// Deprecated alias carrying the same elapsed value; see
727727
// PluginStartupResult.startTime.
728728
startTime: duration,

packages/core/src/plugin-loader.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,21 @@ export interface PluginStartupResult {
9494
/**
9595
* Elapsed milliseconds the plugin's `start()` took.
9696
*
97-
* Named for the member `packages/spec` already declares for the same
98-
* measure -- `PluginStartupResultSchema.duration` in
97+
* Named for the member `packages/spec` declares for the same measure --
98+
* `PluginStartupResultSchema.durationMs` in
9999
* `packages/spec/src/kernel/startup-orchestrator.zod.ts` ("Time taken to
100-
* start the plugin in milliseconds") -- and matching `PluginLoadResult.loadTime`
101-
* above: the same `Date.now() - startTime` computation under a name that
102-
* does not lie.
100+
* start the plugin in milliseconds"), where the bare `duration` spelling is
101+
* retired: a duration-shaped number carries its unit in its key name. Like
102+
* `PluginLoadResult.loadTime` above, it is the same `Date.now() - startTime`
103+
* computation under a name that does not lie.
103104
*/
104-
duration?: number;
105+
durationMs?: number;
105106
/**
106-
* The same elapsed milliseconds as {@link PluginStartupResult.duration}.
107+
* The same elapsed milliseconds as {@link PluginStartupResult.durationMs}.
107108
*
108109
* @deprecated Misnamed: this has never held an instant, so a reader who
109110
* correctly takes `startTime` for one and writes `Date.now() - result.startTime`
110-
* gets an age near the epoch instead of a wait. Read `duration` instead.
111+
* gets an age near the epoch instead of a wait. Read `durationMs` instead.
111112
* Still populated so nothing has to change on this release (ADR-0087 L1 --
112113
* the old shape keeps working while the fleet moves); slated for removal.
113114
*/

0 commit comments

Comments
 (0)