Skip to content

Commit 74ddc0d

Browse files
claude[bot]claude
andauthored
feat(spec)!: the fourteen kernel/ duration keys carry their unit in the key name (#15678, stack 3/6) (#15906)
* feat(spec): declare the two duration-rule exemptions on the schema (#15676) Ruling B on #14478 exempts two structural classes from the duration-unit rule, and is explicit that both are declared ON THE SCHEMA, never in a gate ledger. This commit lands the declaration channels themselves: - `EpochMs` (`packages/spec/src/shared/epoch.zod.ts`) — the shared epoch-milliseconds instant. A key whose value IS this schema is an instant, not a duration, and `check:duration-unit-keys` recognises that structurally. - `.meta({ externalVocabulary: '<the standard>' })` — the marker a key carries when it mirrors a name fixed outside this repo. It rides `z.toJSONSchema` verbatim, the same channel `xRef` / `xExpression` already use. Neither exemption is a pass on lying: a marked key still fails `name-unit-contradicts-prose`, and an `EpochMs` key whose describe names a unit other than milliseconds fails the new `instant-unit-contradicts-schema`. Both classes stay visible in the census — `--list` marks them and the verdict line counts them. The gate also now reads `description` out of `.meta()`. Without it, moving a describe into `.meta({ description })` would take a key out of the population silently — an exemption by blindness. Measured: one numeric key declares its description that way today (`data/Field.precision`), naming no time unit, so the reading adds no offender. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * feat(spec)!: move the six epoch instants onto EpochMs and mark the external-vocabulary keys (#15676) The two exemption classes ruling B declares, applied to the keys the gate lists. Instants (exemption i) — all six now typed `EpochMs`; the four whose name was bare are renamed to the `*At` instant convention, tombstoned with `retiredKey()` and registered in `RETIRED_KEYS_BY_MAJOR[18]` plus one D3 semantic entry: api/WebSocketEvent.timestamp -> occurredAt api/SimplePresenceState.lastSeen -> lastSeenAt kernel/KernelContext.startTime -> startedAt (+ TenantRuntimeContext) kernel/HealthStatus.timestamp -> checkedAt kernel/ServiceMetadata.registeredAt (already `*At`, schema only) kernel/ScopeInfo.createdAt (already `*At`, schema only) `*At` and not `*Ms`, measured rather than chosen: on this package's own authorable surface all 51 distinct `*Ms` keys are durations and all 51 distinct `*At` keys are instants, so spelling an instant `*Ms` would move it into the family the rule exists to separate it from. Semantic entries rather than D2 conversions because all four are runtime-emitted — wire payloads, a host-constructed kernel context, an emitted health report — so no conversion seam ever sees one. That is the disposition `kernel/KernelContext:previewMode` already carries on one of these defs, and what ruling B prescribes for a runtime-emitted key. External-standard mirrors (exemption ii) — eleven keys marked, not thirteen. Two of the thirteen the card attributed do not survive verification against their own schema and are left for their directory cards; the PR body records the evidence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * feat(spec): publish the externalVocabulary standard on the reference page (#15676) The published half of exemption (ii). A marked key keeps its bare name BECAUSE an external standard fixes it, and that argument only reaches the reference-page reader if the page names the standard — so the description cell now carries "(unit per <the standard>)". Without it the exemption would leave exactly the reader `check:duration-unit-keys` was filed for where the gate found them. Also: `EpochMs` gains its type alias (the docs import-surface ratchet demands one for every documented schema) and its ADR-0122 isomorphism pin. Regenerated: json-schema.manifest/, authorable-surface/, api-surface/, export-origins/, declaration-map/, content/docs/references/**. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs(changeset): the two duration-rule exemptions (#15676) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * chore(spec): regenerate the derived artifacts and fix the consumers the tombstones caught (#15676) - `src/contracts/startup-orchestrator.test.ts` built a `HealthStatus` with the old `timestamp` key. The `retiredKey()` tombstone refused it at compile time (`Type 'number' is not assignable to type 'undefined'`, 7 errors) — the audible-removal property the tombstone exists for, working on the first consumer it met. - `type-alias-convention.pin.test.ts`: the pin count assertion follows the new `Iso868`. - Regenerated: export-origins/, declaration-map/, api-surface/ and the `objectstack-api` skill reference index (one generated line, naming the new `shared/epoch.zod.ts` module). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): rename the 12 api/ duration keys, tombstones on the old spellings (#15677) The schema half of stack card 2/6. Gate reads 48 -> 36 with src/api/ at 0. Readers, registry entries and regenerated artifacts follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): readers, ADR-0087 registrations, ledger row for the api/ renames (#15677) 12 retired-key entries, one D2 conversion (api-endpoint-cache-ttl-to-cache-ttl-seconds — apis: is a stack collection) and five semantic entries for the eleven runtime-emitted / construction-argument keys. Readers moved in runtime, metadata, rest-adjacent tests, dogfood fixtures and the showcase example; liveness/api.json carries the renamed row plus the dead tombstone row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): tombstone refusal tests, alias retarget, regenerated artifacts (#15677) Per-key refusal tests assert the prescription (code + rename text), not a bare throw. Two readers the key-name grep missed and tsc/the tombstones caught: the ApiEndpoint alias table (cacheTTL/ttl/cache retargeted onto cacheTtlSeconds — an alias must point at a key the schema accepts) and the showcase endpoint fixture in metadata-type-api-registration.test.ts. Regenerated: authorable surface + defaults, reference docs, liveness state-counts. skills/objectstack-api/SKILL.md carries the rename (governed; net 0 lines, file and package both). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs(changeset): the twelve api/ duration renames (#15677) @objectstack/spec minor with the BREAKING banner naming every renamed key, the six adr-0087 ids registered, the retryAfter wire note, and the disposition split (one D2 conversion, five semantic entries). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * fix(docs-audit): declare the conversion-replay exclusion kind (b) relied on incidentally (#15677) An ADR-0087 conversion fixture copies a routable metadata kind verbatim, so an `apis:` fixture carries `method:` beside `path:` — because that is what an ApiEndpoint IS. Ruling A named conversions/registry.ts as the guard's target but enforced it with requireMethodSignal, a content proxy that held only while no conversion fixture carried a verb. This card's apis: conversion is the first that does, and the live pin red exactly as designed. The fixture is correct and stays. The exclusion moves to CONVERSION_REPLAY_FILE_RE, which states the structural fact instead of testing a symptom, and three cases pin the new guard as load-bearing rather than incidental in its turn. NOT restricting kind (b) to packages/spec/src/api/**: that is the invariant the live pin asserts, and enforcing it in the walk would make that pin true by construction — a check that cannot fail. Measured tail-neutral: the scan census is byte-identical to the base (17 route sources, 12 call sites, 5 contract declarations, 78 tails, 61 reachable). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs: move the hand-written pages onto the renamed keys, and strip the issue ids (#15677) Lap 1 regenerated content/docs/references/** but left the HAND-WRITTEN pages teaching the old spellings. Three of them carried `os:check` blocks authoring `cacheTtl`, so check:skill-examples was RED and lap 1 never ran it — it sits in check:generated's "not run here" list and I did not run it separately. cacheTtl -> cacheTtlSeconds: 14 occurrences on 13 lines, all the ApiEndpoint key. retryAfter -> retryAfterSeconds: 14 occurrences, the ADR-0112 envelope field only. Deliberately NOT swept, each verified rather than assumed: - the HTTP `Retry-After` response header (6 locals over 4 sites) — RFC 9110, a separate surface, and the thing the tombstone prose exists to protect; - `retry_after` as a RetryStrategy ENUM VALUE (errors.zod.ts z.enum); - `details.retry_after` on the wire, and the pre-existing `details.retryAfterSeconds` the runtime really emits (endpoint-policy.ts). Also strips `(#14478 ruling B)` from the twelve tombstone prescriptions THIS card wrote: check:doc-authoring forbids an internal issue id in customer-facing spec text (maintainer ruling 2026-08-12), and the campaign's own earlier tombstones already comply. The version and the FROM -> TO mapping stay — those are the durable references AGENTS.md requires. Measured: the gate read 4 findings on the base and 16 on my head; it now reads the base's 4 again, so this PR adds none. Those 4 are card 1/6's (PR #15814) and are not mine to touch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs(spec): drop the internal issue id from the four epoch-instant tombstone prescriptions (#15676) `check:doc-authoring` rule 3 forbids an internal issue id in customer-facing spec text (maintainer ruling 2026-08-12). The four `retiredKey()` prescriptions this stack card added each opened with a `(#14478 ruling B)` parenthetical — a citation-shaped token that resolves to nothing for the author who meets it the moment their key is refused, and which the generated reference pages publish verbatim. The parenthetical goes; the sentence keeps everything actionable it carried — the FROM to TO rename, that the value is unchanged, and the `SimplePresenceState.lastSeen` neighbour caveat — matching the shape the campaign's already-compliant tombstones use (`hook.timeout`, `job.timeout`, `DriverOptions.timeout`). The internal anchor is untouched in the adjacent `//` and `/** */` comments, which are not customer-facing and were never findings. `content/docs/references/**` regenerated with `pnpm --filter @objectstack/spec gen:docs` — no generated artifact was hand-edited. check:doc-authoring: 4 findings before, exit 0 after. check:duration-unit-keys: unmoved — 48 offender(s) among 215 duration-shaped numeric key(s), (6 declared `EpochMs` instant(s), 11 declared `externalVocabulary` mirror(s)). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * chore(spec): regenerate the reference page the merge deferred (#15677) The merge with card 1/6's advanced tip (9605808) touched api/websocket.zod.ts on both sides. The schema source auto-merged; the generated content/docs/references/api/websocket.mdx is routed to merge=os-regen, so the driver deferred it and the merge kept OUR side — silently dropping card 1/6's half. Regenerating from the merged tree is what repairs it, and it carries both sides: their two stripped prescriptions land (issue-id occurrences 2 -> 0) while my four renamed keys stay (6 -> 6). Not hand-edited and not resolved by taking a side: the bytes come from `pnpm --filter @objectstack/spec check:generated --fix` on the merged tree, and the staged diff was read before committing (`git diff` reads clean over this trap; only the staged diff shows it). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): rename the 14 kernel/ duration keys, tombstones on the old spellings (#15678) The schema half of stack card 3/6. Gate reads 36 -> 22 with src/kernel/ at 0. Readers, registrations and regenerated artifacts follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): readers and ADR-0087 registrations for the kernel/ renames (#15678) 14 retired-key entries and five semantic entries. No D2 conversion on this card: none of the twelve defs is a stack collection member or a stored sys_metadata row (stack.zod.ts declares no eventBus / startup / plugin-security root), so the conversion chain has no seam that would see one. Readers moved in core's health monitor, the kernel and contracts test suites, and the hand-written lifecycle protocol page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * wip(spec): tombstone refusal tests and regenerated reference pages (#15678) Per-key refusal tests assert the prescription (issue code + rename text), not a bare throw, plus acceptance pins at the same magnitudes and defaults. Two deliberate NEGATIVE controls: RuntimeConfig.resourceLimits.timeout names no unit so it stays bare, and EventSourcingConfig.snapshotRetention is a count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs(changeset): the fourteen kernel/ duration renames (#15678) @objectstack/spec minor + @objectstack/core patch, BREAKING banner naming every renamed key, adr-0087: registered with the five semantic ids. Documents the two unit collisions the rename removes (responseTime hours vs ms; uptime ms vs the seconds-valued GET /health) and the three keys deliberately left bare. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * docs(changeset): correct why `RuntimeConfig.resourceLimits.timeout` is left bare (#15678) The changeset stated the key "names no unit anywhere in its prose". That is false and it would have published into the CHANGELOG: the JSDoc directly above the key (plugin-security-advanced.zod.ts:291-293) says "Execution timeout in milliseconds". Its `.describe('Maximum execution time')` names none. The accurate reason is stronger: `check:duration-unit-keys` reads `.describe()` and `.meta({ description })` and does NOT read JSDoc, so this key's unit sits in a channel the gate cannot see. The gate therefore lists it among the duration-shaped keys but deliberately does not judge it — neither an offender nor an exemption. That channel gap is tracked in #15939. The same false clause appeared in three more places on this branch and is corrected in all of them: the ADR-0087 semantic migration entry (its `reason` publishes into the generated upgrade guide), its generated mirror in registry.ts (via gen:migration-registry, never hand-edited), and the comment above the pin test. Prose only. No key renamed, no schema, `.describe()`, JSDoc, tombstone or pin assertion touched; the key stays bare and stays out of the rename. `check:duration-unit-keys` still reads 22 offender(s) among 215 duration-shaped numeric key(s) in 805 source file(s), `kernel/` at 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 * chore(spec): regenerate the os-regen artifacts on the merged tree Discharges the deferral the merge commit recorded. `check:generated` proved exactly one artifact set stale (content/docs/references/**) after the spec build re-derived authorable-surface/kernel.json, so only that one was regenerated. Both files re-derive this card's [RETIRED] rows on top of the trunk's side; the trunk's own renames (checkedAt, startedAt) survive in both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G4138K1EG7kQ81FNba5Kp4 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 43cc465 commit 74ddc0d

52 files changed

Lines changed: 1445 additions & 113 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/core": patch
4+
---
5+
6+
feat(spec)!: the fourteen `kernel/` duration keys carry their unit in the key name (#15678, ruling B on #14478)
7+
8+
<!-- adr-0087: registered kernel-event-bus-retention-unit-in-key, kernel-package-lifecycle-durations-unit-in-key, kernel-plugin-health-report-durations-unit-in-key, kernel-plugin-security-durations-unit-in-key, kernel-startup-orchestrator-durations-unit-in-key -->
9+
10+
**BREAKING** — fourteen published `kernel/` duration keys are renamed and
11+
tombstoned. Shipped as `minor` under the repo's launch-window convention for
12+
breaking changes; the hand-migration prescriptions are registered under protocol
13+
major 18. Maintainer ruling B on #14478 (2026-09-02, decision batch #43,
14+
「同意」).
15+
16+
`check:duration-unit-keys` makes a duration-shaped `z.number()` carry its unit
17+
in the key NAME, never only in its `.describe()` prose, and grandfathers no
18+
existing offender. Stack card 1/6 (#15676) landed the rule's two structural
19+
exemptions and card 2/6 (#15677) cleared `api/`; this card clears `kernel/`.
20+
Measured with the gate itself: `src/kernel/**` goes from 14 offenders to **0**,
21+
and the whole-tree count falls **36 → 22**.
22+
23+
## FROM → TO
24+
25+
| key | replacement | unit |
26+
|:--|:--|:--|
27+
| `EventPersistence.retention` | `retentionDays` | days |
28+
| `EventSourcingConfig.retention` | `retentionDays` | days |
29+
| `UpgradePlan.estimatedDuration` | `estimatedDurationSeconds` | seconds |
30+
| `PluginHealthReport.metrics.uptime` | `uptimeMs` | milliseconds |
31+
| `PluginHealthReport.metrics.responseTime` | `responseTimeMs` | milliseconds |
32+
| `SandboxConfig.process.timeout` | `timeoutMs` | milliseconds |
33+
| `KernelSecurityPolicy.authentication.tokenExpiration` | `tokenExpirationSeconds` | seconds |
34+
| `KernelSecurityPolicy.auditLog.retention` | `retentionDays` | days |
35+
| `PluginSecurityManifest.vulnerabilityDisclosure.responseTime` | `responseTimeHours` | hours |
36+
| `PackageDependencyResolutionResult.resolvedIn` | `resolvedInMs` | milliseconds |
37+
| `MultiVersionSupport.rollout.duration` | `durationMs` | milliseconds |
38+
| `StartupOptions.timeout` | `timeoutMs` | milliseconds |
39+
| `PluginStartupResult.duration` | `durationMs` | milliseconds |
40+
| `StartupOrchestrationResult.totalDuration` | `totalDurationMs` | milliseconds |
41+
42+
**Every value is unchanged** — only key names move, and every default moves with
43+
its key (`StartupOptions` still defaults to 30000, `EventSourcingConfig` to
44+
365). Every old spelling is a `retiredKey()` tombstone, so it fails `tsc` at the
45+
authoring site (input type `never`) and fails the parse with the rename
46+
prescription rather than a bare unrecognized-key error.
47+
48+
## ⚠️ Two collisions this rename removes — check these by hand, not by search-and-replace
49+
50+
**`responseTime` meant two different units on two kernel shapes.** On
51+
`PluginSecurityManifest.vulnerabilityDisclosure` it is HOURS (how fast a
52+
publisher promises to answer a vulnerability report); on
53+
`PluginHealthReport.metrics` the identical bare name is MILLISECONDS. So
54+
`responseTime: 24` was a day on one shape and a fortieth of a second on the
55+
other, with nothing at the authoring site to tell them apart. They land on
56+
`responseTimeHours` and `responseTimeMs` respectively — do not let one
57+
find-and-replace rewrite both.
58+
59+
**`uptime` is milliseconds here and SECONDS on `GET /health`.** That collision
60+
was already costing prose: the protocol lifecycle page carried a standing
61+
paragraph whose only job was telling the two apart. `metrics.uptime` becomes
62+
`metrics.uptimeMs`; the seconds-valued `uptime` of the HTTP health body is a
63+
separate, unchanged surface and must not be renamed with it.
64+
65+
A third split worth reading before you migrate: `estimatedDurationSeconds: 120`
66+
is two MINUTES while `durationMs: 3600000` is one HOUR. Three adjacent
67+
measurements of the same package install carried two different units, and no
68+
parse can catch a value moved between them — both bounds accept any
69+
non-negative integer.
70+
71+
## Dispositions — five semantic entries, no D2 conversion
72+
73+
Justified per key rather than defaulted, and this card's answer is uniform:
74+
**none of the fourteen gets an ADR-0087 D2 conversion.** A D2 conversion runs
75+
over a stack document, and `stack.zod.ts` declares no `eventBus`, `startup`,
76+
`upgrade` or plugin-security root — none of these twelve defs is a stack
77+
collection member or a registered metadata kind stored as a `sys_metadata` row,
78+
so the conversion chain has no seam that would see one. They are host
79+
construction arguments (`EventBusConfig`, `StartupOptions`, `SandboxConfig`,
80+
`MultiVersionSupport`), package artifacts (`PluginSecurityManifest`) and
81+
runtime-emitted measurements (`PluginHealthReport`, `PluginStartupResult`,
82+
`StartupOrchestrationResult`, `UpgradePlan`,
83+
`PackageDependencyResolutionResult`). Each therefore carries a **semantic**
84+
entry, which is the disposition `kernel/HealthStatus:timestamp` already holds on
85+
one of these very files (`epoch-instant-keys-renamed`, card 1/6) and what ruling
86+
B prescribes for a key that is not authorable metadata. All fourteen are
87+
registered by exact key in `RETIRED_KEYS_BY_MAJOR`.
88+
89+
## Keys deliberately left alone
90+
91+
`EventSourcingConfig.snapshotRetention` is a COUNT of snapshots and
92+
`MultiVersionSupport.rollout.percentage` is a proportion — neither is a
93+
duration, so neither has a unit to carry and both keep their names.
94+
`RuntimeConfig.resourceLimits.timeout` names its unit only in the JSDoc above
95+
the key ("Execution timeout in milliseconds"), a channel
96+
`check:duration-unit-keys` does not read: it reads `.describe()` and
97+
`.meta({ description })`, and this key's describe ("Maximum execution time")
98+
names none. The gate therefore lists it among the duration-shaped keys but
99+
deliberately does not judge it — neither an offender nor an exemption — so it is
100+
outside this rename; that JSDoc-channel gap is filed as #15939. A pin test
101+
asserts the key still parses bare, so a later sweep cannot read the four
102+
security renames as "every timeout on that file".
103+
104+
## Readers moved in the same PR, at the same magnitude
105+
106+
`@objectstack/core`'s health monitor (`metrics.uptimeMs: Date.now() -
107+
startTime`), the kernel and contracts test suites, and the hand-written
108+
`content/docs/protocol/kernel/lifecycle.mdx`, whose `uptime` paragraph now
109+
states the collision the rename removes.
110+
111+
⚠️ `packages/core/src/plugin-loader.ts` declares its OWN local
112+
`PluginStartupResult` interface — a different type, carrying `startTime` rather
113+
than any duration key. It is not a reader of this schema, it is untouched by
114+
this rename, and the divergence between the two shapes is tracked separately.

content/docs/protocol/kernel/lifecycle.mdx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -770,7 +770,7 @@ this shape over HTTP — it is an in-process model, not a wire body.
770770
"status": "healthy",
771771
"timestamp": "2024-01-15T11:00:00.000Z",
772772
"metrics": {
773-
"uptime": 3600000
773+
"uptimeMs": 3600000
774774
},
775775
"checks": [
776776
{ "name": "healthCheck", "status": "passed" }
@@ -789,12 +789,13 @@ this shape over HTTP — it is an in-process model, not a wire body.
789789
| `"plugin-loaded"` | no `checkMethod` is configured, **or** the configured name does not resolve to a function on the plugin |
790790
| `"health-check"` | the check **threw** — a `timeout` overrun included, since the race surfaces it as a rejection. A fixed name, neither the method's nor the default's, and always `status: "failed"` |
791791

792-
`metrics.uptime` is in **milliseconds** (`Date.now() - startTime`), unlike
793-
the seconds-valued `uptime` of `GET /health` above, and the report carries no
792+
`metrics.uptimeMs` is in **milliseconds** (`Date.now() - startTime`), unlike
793+
the seconds-valued `uptime` of `GET /health` above — which is the very
794+
collision the unit-in-the-key-name rule exists to remove — and the report carries no
794795
`version` field — it identifies its plugin by the key it is stored under. The
795796
optional `message` is set only when a check fails; the schema's remaining
796797
`metrics` fields (`memoryUsage`, `cpuUsage`, `activeConnections`, `errorRate`,
797-
`responseTime`) and its `dependencies` array are declared but left unset by the
798+
`responseTimeMs`) and its `dependencies` array are declared but left unset by the
798799
monitor today.
799800

800801
## Shutdown Sequence

content/docs/references/kernel/events-bus.mdx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ const result = EventBusConfigSchema.parse(data);
2727

2828
| Property | Type | Required | Description |
2929
| :--- | :--- | :--- | :--- |
30-
| **persistence** | `{ enabled: boolean; retention: integer; filter?: any; storage: Enum<'database' \| 'file' \| 's3' \| 'custom'> }` | optional | Event persistence configuration |
30+
| **persistence** | `{ enabled: boolean; retentionDays: integer; filter?: any; storage: Enum<'database' \| 'file' \| 's3' \| 'custom'> }` | optional | Event persistence configuration |
3131
| **queue** | `{ name: string; concurrency: integer; retryPolicy?: object; deadLetterQueue?: string; … }` | optional | Event queue configuration |
32-
| **eventSourcing** | `{ enabled: boolean; snapshotInterval: integer; snapshotRetention: integer; retention: integer; … }` | optional | Event sourcing configuration |
32+
| **eventSourcing** | `{ enabled: boolean; snapshotInterval: integer; snapshotRetention: integer; retentionDays: integer; … }` | optional | Event sourcing configuration |
3333
| **replay** | `{ enabled: boolean }` | optional | Event replay configuration |
3434
| **webhooks** | `{ id?: string; eventPattern: string; url: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'PATCH'>; … }[]` | optional | Webhook configurations |
3535
| **messageQueue** | `{ provider: Enum<'kafka' \| 'rabbitmq' \| 'aws-sqs' \| 'redis-pubsub' \| 'google-pubsub' \| 'azure-service-bus'>; topic: string; eventPattern: string; partitionKey?: string; … }` | optional | Message queue integration |
@@ -42,7 +42,8 @@ const result = EventBusConfigSchema.parse(data);
4242
| Property | Type | Required | Description |
4343
| :--- | :--- | :--- | :--- |
4444
| **enabled** | `boolean` | optional (default: `false`) | Enable event persistence |
45-
| **retention** | `integer` || Days to retain persisted events |
45+
| **retentionDays** | `integer` || Days to retain persisted events |
46+
| **retention** | `never` | optional | [REMOVED] `EventPersistence.retention` was renamed to `retentionDays` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `retentionDays`; the value (days) is unchanged. |
4647
| **filter** | `any` | optional | Optional filter function to select which events to persist |
4748
| **storage** | `Enum<'database' \| 'file' \| 's3' \| 'custom'>` | optional (default: `"database"`) | Storage backend for persisted events |
4849

@@ -63,7 +64,8 @@ const result = EventBusConfigSchema.parse(data);
6364
| **enabled** | `boolean` | optional (default: `false`) | Enable event sourcing |
6465
| **snapshotInterval** | `integer` | optional (default: `100`) | Create snapshot every N events |
6566
| **snapshotRetention** | `integer` | optional (default: `10`) | Number of snapshots to retain |
66-
| **retention** | `integer` | optional (default: `365`) | Days to retain events |
67+
| **retentionDays** | `integer` | optional (default: `365`) | Days to retain events |
68+
| **retention** | `never` | optional | [REMOVED] `EventSourcingConfig.retention` was renamed to `retentionDays` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `retentionDays`; the value (days) is unchanged. The neighbouring `snapshotRetention` is a COUNT of snapshots, not a duration, so it keeps its name. |
6769
| **aggregateTypes** | `string[]` | optional | Aggregate types to enable event sourcing for |
6870
| **storage** | `{ type: Enum<'database' \| 'file' \| 's3' \| 'eventstore'>; options?: Record<string, any> }` | optional | Event store configuration |
6971

content/docs/references/kernel/events-handlers.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ const result = EventHandlerSchema.parse(data);
5454
| Property | Type | Required | Description |
5555
| :--- | :--- | :--- | :--- |
5656
| **enabled** | `boolean` | optional (default: `false`) | Enable event persistence |
57-
| **retention** | `integer` || Days to retain persisted events |
57+
| **retentionDays** | `integer` || Days to retain persisted events |
58+
| **retention** | `never` | optional | [REMOVED] `EventPersistence.retention` was renamed to `retentionDays` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `retentionDays`; the value (days) is unchanged. |
5859
| **filter** | `any` | optional | Optional filter function to select which events to persist |
5960
| **storage** | `Enum<'database' \| 'file' \| 's3' \| 'custom'>` | optional (default: `"database"`) | Storage backend for persisted events |
6061

content/docs/references/kernel/events-queue.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ const result = EventQueueConfigSchema.parse(data);
7070
| **enabled** | `boolean` | optional (default: `false`) | Enable event sourcing |
7171
| **snapshotInterval** | `integer` | optional (default: `100`) | Create snapshot every N events |
7272
| **snapshotRetention** | `integer` | optional (default: `10`) | Number of snapshots to retain |
73-
| **retention** | `integer` | optional (default: `365`) | Days to retain events |
73+
| **retentionDays** | `integer` | optional (default: `365`) | Days to retain events |
74+
| **retention** | `never` | optional | [REMOVED] `EventSourcingConfig.retention` was renamed to `retentionDays` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `retentionDays`; the value (days) is unchanged. The neighbouring `snapshotRetention` is a COUNT of snapshots, not a duration, so it keeps its name. |
7475
| **aggregateTypes** | `string[]` | optional | Aggregate types to enable event sourcing for |
7576
| **storage** | `{ type: Enum<'database' \| 'file' \| 's3' \| 'eventstore'>; options?: Record<string, any> }` | optional | Event store configuration |
7677

content/docs/references/kernel/package-upgrade.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ Upgrade package response
198198
| **requiresMigration** | `boolean` | optional (default: `false`) | Whether data migration scripts are needed |
199199
| **migrationScripts** | `string[]` | optional | Paths to migration scripts |
200200
| **dependencyUpgrades** | `{ packageId: string; fromVersion: string; toVersion: string }[]` | optional | Dependent packages that also need upgrading |
201-
| **estimatedDuration** | `integer` | optional | Estimated upgrade duration in seconds |
201+
| **estimatedDurationSeconds** | `integer` | optional | Estimated upgrade duration in seconds |
202+
| **estimatedDuration** | `never` | optional | [REMOVED] `UpgradePlan.estimatedDuration` was renamed to `estimatedDurationSeconds` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `estimatedDurationSeconds`; the value (seconds) is unchanged. |
202203
| **summary** | `string` | optional | Human-readable upgrade summary |
203204

204205

@@ -241,7 +242,8 @@ Upgrade analysis plan generated before execution
241242
| **requiresMigration** | `boolean` | optional (default: `false`) | Whether data migration scripts are needed |
242243
| **migrationScripts** | `string[]` | optional | Paths to migration scripts |
243244
| **dependencyUpgrades** | `{ packageId: string; fromVersion: string; toVersion: string }[]` | optional | Dependent packages that also need upgrading |
244-
| **estimatedDuration** | `integer` | optional | Estimated upgrade duration in seconds |
245+
| **estimatedDurationSeconds** | `integer` | optional | Estimated upgrade duration in seconds |
246+
| **estimatedDuration** | `never` | optional | [REMOVED] `UpgradePlan.estimatedDuration` was renamed to `estimatedDurationSeconds` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `estimatedDurationSeconds`; the value (seconds) is unchanged. |
245247
| **summary** | `string` | optional | Human-readable upgrade summary |
246248

247249
### Nested Shape: `UpgradePlan.changes[number]`

content/docs/references/kernel/plugin-lifecycle-advanced.mdx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,22 @@ const result = HotReloadConfigSchema.parse(data);
7979
| **status** | `Enum<'healthy' \| 'degraded' \| 'unhealthy' \| 'failed' \| 'recovering' \| 'unknown'>` || Current health status of the plugin |
8080
| **timestamp** | `string` || |
8181
| **message** | `string` | optional | |
82-
| **metrics** | `{ uptime?: number; memoryUsage?: number; cpuUsage?: number; activeConnections?: number; … }` | optional | |
82+
| **metrics** | `{ uptimeMs?: number; memoryUsage?: number; cpuUsage?: number; activeConnections?: number; … }` | optional | |
8383
| **checks** | `{ name: string; status: Enum<'passed' \| 'failed' \| 'warning'>; message?: string; data?: Record<string, any> }[]` | optional | |
8484
| **dependencies** | `{ pluginId: string; status: Enum<'healthy' \| 'degraded' \| 'unhealthy' \| 'failed' \| 'recovering' \| 'unknown'>; message?: string }[]` | optional | |
8585

8686
### Nested Shape: `PluginHealthReport.metrics`
8787

8888
| Property | Type | Required | Description |
8989
| :--- | :--- | :--- | :--- |
90-
| **uptime** | `number` | optional | Plugin uptime in milliseconds |
90+
| **uptimeMs** | `number` | optional | Plugin uptime in milliseconds |
9191
| **memoryUsage** | `number` | optional | Memory usage in bytes |
9292
| **cpuUsage** | `number` | optional | CPU usage percentage |
9393
| **activeConnections** | `number` | optional | Number of active connections |
9494
| **errorRate** | `number` | optional | Error rate (errors per minute) |
95-
| **responseTime** | `number` | optional | Average response time in ms |
95+
| **responseTimeMs** | `number` | optional | Average response time in ms |
96+
| **uptime** | `never` | optional | [REMOVED] `PluginHealthReport.metrics.uptime` was renamed to `uptimeMs` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose, and this platform already spells a SECONDS-valued uptime with the same bare name on GET /health. Rename the key to `uptimeMs`; the value (milliseconds, `Date.now() - startTime`) is unchanged. |
97+
| **responseTime** | `never` | optional | [REMOVED] `PluginHealthReport.metrics.responseTime` was renamed to `responseTimeMs` in @objectstack/spec 17 — the unit of a duration-shaped number lives in the key name, not only in the describe prose. Rename the key to `responseTimeMs`; the value (milliseconds) is unchanged. |
9698

9799
### Nested Shape: `PluginHealthReport.checks[number]`
98100

0 commit comments

Comments
 (0)