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
26 changes: 26 additions & 0 deletions .changeset/kernel-service-status-dual-source.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
---
"@objectstack/spec": minor
---

BREAKING(spec): `@objectstack/spec/system` renames `ServiceStatusSchema` → `KernelServiceStatusSchema` and gains the `KernelServiceStatus` type alias (#6604, ADR-0112 D9a)

`ServiceStatus` was published by two entry points for two disjoint concepts: `./api`'s
discovery health **enum** (`api/discovery.zod.ts`) and `./system`'s kernel service **state
object** (`system/core-services.zod.ts`, a `features`-bearing record). Per the maintainer
ruling of 2026-08-08 (Option B) the kernel side takes the domain-specific name, matching
its `KernelServiceMapSchema` sibling in the same file:

- `ServiceStatusSchema` → `KernelServiceStatusSchema` (`@objectstack/spec/system`)
- new: `export type KernelServiceStatus` — the alias `#4593`'s backfill had to skip,
because declaring `ServiceStatus` on both entry points would have minted the #4411
dual-source trap
- JSON Schema def `system/ServiceStatus` → `system/KernelServiceStatus`, carried through
`RENAMED_DEFS` with all 6 authorable keys (`enabled` / `features` / `name` / `provider` /
`status` / `version`) intact

**`@objectstack/spec/api`'s `ServiceStatus` is untouched** — it keeps its published name,
so no consumer of the discovery health enum changes. Nothing left the author-facing
contract: the renamed def re-emits every key byte-for-byte, which is why this rides the
rename table rather than the retirement kit. Consumers importing `ServiceStatusSchema`
from `@objectstack/spec/system` update the name; `tsc` reports TS2724/TS2305 on any that
does not.
2 changes: 1 addition & 1 deletion content/docs/references/index.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -330,7 +330,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
| [`cache.zod.ts`](/docs/references/system/cache) | `CacheAvalanchePrevention`, `CacheConfig`, `CacheConsistency`, `CacheInvalidation`, `CacheStrategy`, `CacheTier`, `CacheWarmup`, `DistributedCacheConfig` |
| [`change-management.zod.ts`](/docs/references/system/change-management) | `ChangeImpact`, `ChangePriority`, `ChangeRequest`, `ChangeStatus`, `ChangeType`, `RollbackPlan` |
| [`collaboration.zod.ts`](/docs/references/system/collaboration) | `AwarenessEvent`, `AwarenessSession`, `AwarenessUpdate`, `AwarenessUserState`, `CRDTMergeResult`, `CRDTState`, `CRDTType`, `CollaborationMode`, `CollaborationSession`, `CollaborationSessionConfig`, `CollaborativeCursor`, `CounterOperation`, `CursorColorPreset`, `CursorSelection`, `CursorStyle`, `CursorUpdate`, `GCounter`, `LWWRegister`, `ORSet`, `ORSetElement`, `OTComponent`, `OTOperation`, `OTOperationType`, `OTTransformResult`, `PNCounter`, `TextCRDTOperation`, `TextCRDTState`, `UserActivityStatus`, `VectorClock` |
| [`core-services.zod.ts`](/docs/references/system/core-services) | `CoreServiceName`, `KernelServiceMap`, `ServiceConfig`, `ServiceCriticality`, `ServiceStatus` |
| [`core-services.zod.ts`](/docs/references/system/core-services) | `CoreServiceName`, `KernelServiceMap`, `KernelServiceStatus`, `ServiceConfig`, `ServiceCriticality` |
| [`deploy-bundle.zod.ts`](/docs/references/system/deploy-bundle) | `DeployBundle`, `DeployDiff`, `DeployManifest`, `DeployStatusEnum`, `DeployValidationIssue`, `DeployValidationResult`, `MigrationPlan`, `MigrationStatement`, `SchemaChange` |
| [`disaster-recovery.zod.ts`](/docs/references/system/disaster-recovery) | `BackupConfig`, `BackupRetention`, `BackupStrategy`, `DisasterRecoveryPlan`, `FailoverConfig`, `FailoverMode`, `RPO`, `RTO` |
| [`doc.zod.ts`](/docs/references/system/doc) | `Doc` |
Expand Down
36 changes: 18 additions & 18 deletions content/docs/references/system/core-services.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,8 +20,8 @@ This registry is used by the `ObjectKernel` and `HttpDispatcher` to:
## TypeScript Usage

```typescript
import { CoreServiceName, KernelServiceMapSchema, ServiceConfigSchema, ServiceCriticalitySchema, ServiceStatusSchema } from '@objectstack/spec/system';
import type { CoreServiceName, KernelServiceMap, ServiceConfig, ServiceCriticality } from '@objectstack/spec/system';
import { CoreServiceName, KernelServiceMapSchema, KernelServiceStatusSchema, ServiceConfigSchema, ServiceCriticalitySchema } from '@objectstack/spec/system';
import type { CoreServiceName, KernelServiceMap, KernelServiceStatus, ServiceConfig, ServiceCriticality } from '@objectstack/spec/system';

// Validate data
const result = CoreServiceName.parse(data);
Expand DownExpand Up@@ -53,6 +53,22 @@ const result = CoreServiceName.parse(data);
---


---

## KernelServiceStatus

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui'>` | ✅ | |
| **enabled** | `boolean` | ✅ | |
| **status** | `Enum<'running' \| 'stopped' \| 'degraded' \| 'initializing'>` | ✅ | |
| **version** | `string` | optional | |
| **provider** | `string` | optional | Implementation provider (e.g. "s3" for storage) |
| **features** | `string[]` | optional | List of supported sub-features |


---

## ServiceConfig
Expand All@@ -79,19 +95,3 @@ const result = CoreServiceName.parse(data);

---

## ServiceStatus

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `Enum<'metadata' \| 'data' \| 'auth' \| 'file-storage' \| 'search' \| 'cache' \| 'queue' \| 'automation' \| 'analytics' \| 'realtime' \| 'job' \| 'notification' \| 'ai' \| 'i18n' \| 'ui'>` | ✅ | |
| **enabled** | `boolean` | ✅ | |
| **status** | `Enum<'running' \| 'stopped' \| 'degraded' \| 'initializing'>` | ✅ | |
| **version** | `string` | optional | |
| **provider** | `string` | optional | Implementation provider (e.g. "s3" for storage) |
| **features** | `string[]` | optional | List of supported sub-features |


---

3 changes: 2 additions & 1 deletion packages/spec/api-surface/system.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -291,6 +291,8 @@
"JobSchema (const)",
"KernelServiceMap (type)",
"KernelServiceMapSchema (const)",
"KernelServiceStatus (type)",
"KernelServiceStatusSchema (const)",
"KeyManagementProvider (type)",
"KeyManagementProviderSchema (const)",
"KeyRotationPolicy (type)",
Expand DownExpand Up@@ -573,7 +575,6 @@
"ServiceLevelObjectiveParsed (type)",
"ServiceLevelObjectiveSchema (const)",
"ServiceRequirementDef (const)",
"ServiceStatusSchema (const)",
"SettingsActionResult (type)",
"SettingsActionResultSchema (const)",
"SettingsChangeEvent (type)",
Expand Down
12 changes: 6 additions & 6 deletions packages/spec/authorable-surface/system.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -540,6 +540,12 @@
"system/JobExecution:jobId",
"system/JobExecution:startedAt",
"system/JobExecution:status",
"system/KernelServiceStatus:enabled",
"system/KernelServiceStatus:features",
"system/KernelServiceStatus:name",
"system/KernelServiceStatus:provider",
"system/KernelServiceStatus:status",
"system/KernelServiceStatus:version",
"system/KeyRotationPolicy:autoRotate",
"system/KeyRotationPolicy:enabled",
"system/KeyRotationPolicy:frequencyDays",
Expand DownExpand Up@@ -1051,12 +1057,6 @@
"system/ServiceLevelObjective:period",
"system/ServiceLevelObjective:sli",
"system/ServiceLevelObjective:target",
"system/ServiceStatus:enabled",
"system/ServiceStatus:features",
"system/ServiceStatus:name",
"system/ServiceStatus:provider",
"system/ServiceStatus:status",
"system/ServiceStatus:version",
"system/SettingsActionResult:details",
"system/SettingsActionResult:message",
"system/SettingsActionResult:ok",
Expand Down
1 change: 0 additions & 1 deletion packages/spec/docs-import-surface.baseline.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,6 @@
"system/AddFieldOperation — no type export",
"system/CreateObjectOperation — no type export",
"system/DeployStatusEnum — no type export",
"system/ServiceStatus — no type export",
"ui/AIChatWindowProps — no type export",
"ui/DerivedMeasureOp — no type export",
"ui/ElementButtonProps — no type export",
Expand Down
3 changes: 2 additions & 1 deletion packages/spec/export-origins/system.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -291,6 +291,8 @@
"JobSchema": "src/system/job.zod.ts#JobSchema (const)",
"KernelServiceMap": "src/system/core-services.zod.ts#KernelServiceMap (type)",
"KernelServiceMapSchema": "src/system/core-services.zod.ts#KernelServiceMapSchema (const)",
"KernelServiceStatus": "src/system/core-services.zod.ts#KernelServiceStatus (type)",
"KernelServiceStatusSchema": "src/system/core-services.zod.ts#KernelServiceStatusSchema (const)",
"KeyManagementProvider": "src/system/encryption.zod.ts#KeyManagementProvider (type)",
"KeyManagementProviderSchema": "src/system/encryption.zod.ts#KeyManagementProviderSchema (const)",
"KeyRotationPolicy": "src/system/encryption.zod.ts#KeyRotationPolicy (type)",
Expand DownExpand Up@@ -573,7 +575,6 @@
"ServiceLevelObjectiveParsed": "src/system/metrics.zod.ts#ServiceLevelObjectiveParsed (type)",
"ServiceLevelObjectiveSchema": "src/system/metrics.zod.ts#ServiceLevelObjectiveSchema (const)",
"ServiceRequirementDef": "src/system/core-services.zod.ts#ServiceRequirementDef (const)",
"ServiceStatusSchema": "src/system/core-services.zod.ts#ServiceStatusSchema (const)",
"SettingsActionResult": "src/system/settings-manifest.zod.ts#SettingsActionResult (type)",
"SettingsActionResultSchema": "src/system/settings-manifest.zod.ts#SettingsActionResultSchema (const)",
"SettingsChangeEvent": "src/system/settings-client.zod.ts#SettingsChangeEvent (type)",
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/json-schema.manifest/system.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,7 @@
"system/JobExecution",
"system/JobExecutionStatus",
"system/KernelServiceMap",
"system/KernelServiceStatus",
"system/KeyManagementProvider",
"system/KeyRotationPolicy",
"system/LWWRegister",
Expand DownExpand Up@@ -222,7 +223,6 @@
"system/ServiceCriticality",
"system/ServiceLevelIndicator",
"system/ServiceLevelObjective",
"system/ServiceStatus",
"system/SettingsActionResult",
"system/SettingsChangeEvent",
"system/SettingsManifest",
Expand Down
24 changes: 24 additions & 0 deletions packages/spec/scripts/lib/renamed-defs.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -137,6 +137,30 @@ export const RENAMED_DEFS: Readonly<Record<string, string>> = {
// always had), which no ratchet here measures and which the changeset states.
// `shared/HttpMethodSubset` is a plain manifest addition.
'ui/HttpMethod': 'ui/HttpMethodSubset',

// #6604 / ADR-0112 D9a — `ServiceStatus` was published by `./api` AND
// `./system` for two disjoint concepts: the discovery HEALTH ENUM
// (`api/discovery.zod.ts`, `z.enum(['operational', ...])`) and the kernel
// service STATE OBJECT (`system/core-services.zod.ts`, a `features`-bearing
// record). Maintainer ruling 2026-08-08 (Option B): the kernel side is
// renamed and `./api` keeps the published bare name untouched — the smallest
// blast radius, because the system side had no type alias yet, so the name
// had not petrified there.
//
// 6 keys carried (`enabled` / `features` / `name` / `provider` / `status` /
// `version`) — every one of them re-emitted byte-for-byte under the new def,
// which is the whole point of routing this through the table rather than the
// retirement kit: NOTHING left the author-facing contract. `api/ServiceStatus`
// is deliberately absent — it is still emitted, by the enum that always
// declared it, so the table would (correctly) reject it as a copy.
//
// This entry is what finally lets the `./system` side declare
// `KernelServiceStatus`: #4593's alias backfill had to skip this one name
// because declaring `ServiceStatus` on both entry points would have minted
// the #4411 dual-source trap `dual-source-exports.baseline.json` stays empty
// to prevent. The rename removes the collision at its source, so the baseline
// stays empty rather than gaining its first exception.
'system/ServiceStatus': 'system/KernelServiceStatus', // 6 keys carried
};

/**
Expand Down
12 changes: 10 additions & 2 deletions packages/spec/scripts/schema-index.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,11 +97,19 @@ describe('exportedValueNames', () => {
});

describe('buildSchemaIndex — cross-category same name', () => {
// The live specimen on `main`: `ServiceStatus` is an enum declared in
// `api/discovery.zod.ts` AND an object declared in
// Drawn from the live specimen this behaviour was found on: `ServiceStatus`
// was an enum declared in `api/discovery.zod.ts` AND an object declared in
// `system/core-services.zod.ts`. The bare-name index let `system` (walked
// later) win, and then placed the API schema on `api/core-services.mdx` —
// a page with no `api/core-services.zod.ts` behind it.
//
// That specimen is no longer live: #6604 renamed the system side to
// `KernelServiceStatusSchema`, so the two categories no longer publish the
// name. The fixture stays SYNTHETIC and unchanged on purpose — what it pins
// is that cross-category same-name resolution is correct whenever it occurs,
// and a rename of one specimen retires the specimen, not the property. Wiring
// the fixture to whatever names happen to collide on `main` today would make
// this coverage evaporate the next time someone tidies those names up.
const idx = index(
file('api', 'discovery.zod.ts', 'export const ServiceStatus = z.enum([]);'),
file('system', 'core-services.zod.ts', 'export const ServiceStatusSchema = z.object({});'),
Expand Down
16 changes: 8 additions & 8 deletions packages/spec/src/system/core-services.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,7 @@ import {
CoreServiceName,
ServiceCriticalitySchema,
ServiceRequirementDef,
ServiceStatusSchema,
KernelServiceStatusSchema,
KernelServiceMapSchema,
ServiceConfigSchema,
} from './core-services.zod';
Expand DownExpand Up@@ -73,9 +73,9 @@ describe('ServiceRequirementDef', () => {
});
});

describe('ServiceStatusSchema', () => {
describe('KernelServiceStatusSchema', () => {
it('should accept valid service status', () => {
const status = ServiceStatusSchema.parse({
const status = KernelServiceStatusSchema.parse({
name: 'metadata',
enabled: true,
status: 'running',
Expand All@@ -90,7 +90,7 @@ describe('ServiceStatusSchema', () => {
const statuses = ['running', 'stopped', 'degraded', 'initializing'];

statuses.forEach((s) => {
expect(() => ServiceStatusSchema.parse({
expect(() => KernelServiceStatusSchema.parse({
name: 'data',
enabled: true,
status: s,
Expand All@@ -99,7 +99,7 @@ describe('ServiceStatusSchema', () => {
});

it('should accept optional fields', () => {
const status = ServiceStatusSchema.parse({
const status = KernelServiceStatusSchema.parse({
name: 'file-storage',
enabled: true,
status: 'running',
Expand All@@ -114,16 +114,16 @@ describe('ServiceStatusSchema', () => {
});

it('should reject invalid service name', () => {
expect(() => ServiceStatusSchema.parse({
expect(() => KernelServiceStatusSchema.parse({
name: 'invalid',
enabled: true,
status: 'running',
})).toThrow();
});

it('should reject missing required fields', () => {
expect(() => ServiceStatusSchema.parse({})).toThrow();
expect(() => ServiceStatusSchema.parse({ name: 'metadata' })).toThrow();
expect(() => KernelServiceStatusSchema.parse({})).toThrow();
expect(() => KernelServiceStatusSchema.parse({ name: 'metadata' })).toThrow();
});
});

Expand Down
13 changes: 11 additions & 2 deletions packages/spec/src/system/core-services.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -224,16 +224,25 @@ export const ServiceRequirementDef = {
// ==========================================

/**
* Describes the availability and health of a service
* Describes the availability and health of a kernel service.
*
* Named `KernelServiceStatus`, not `ServiceStatus`: `./api` publishes its own
* `ServiceStatus` — the discovery health **enum** in `api/discovery.zod.ts` —
* and the two are different concepts (a health value vs this `features`-bearing
* object). One name for two declarations across two entry points is the
* #4411 trap `check:dual-source-exports` guards, so the kernel side carries the
* `Kernel` prefix its sibling `KernelServiceMapSchema` already uses (#6604,
* maintainer ruling 2026-08-08 Option B).
*/
export const ServiceStatusSchema = lazySchema(() => z.object({
export const KernelServiceStatusSchema = lazySchema(() => z.object({
name: CoreServiceName,
enabled: z.boolean(),
status: z.enum(['running', 'stopped', 'degraded', 'initializing']),
version: z.string().optional(),
provider: z.string().optional().describe('Implementation provider (e.g. "s3" for storage)'),
features: z.array(z.string()).optional().describe('List of supported sub-features'),
}));
export type KernelServiceStatus = z.input<typeof KernelServiceStatusSchema>;

/**
* The Contract definition for what the Kernel MUST expose
Expand Down
26 changes: 23 additions & 3 deletions packages/spec/src/type-alias-convention.pin.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -265,7 +265,7 @@ import type * as M167 from './ui/view.zod.js';
import type * as M170 from './ui/component.zod.js';

// ---------------------------------------------------------------------------
// 823 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared.
// 824 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared.
//
// That number is machine-checked, not hand-kept. The runtime companion at the
// bottom of this file recomputes the pin count from the source and asserts that
Expand DownExpand Up@@ -1512,6 +1512,7 @@ export type Iso805 = Assert<Eq< z.input< typeof M119.PanelLocationSchema >, z.in
export type Iso806 = Assert<Eq< z.input< typeof M124.KernelServiceMapSchema >, z.infer< typeof M124.KernelServiceMapSchema > >>;
export type Iso807 = Assert<Eq< z.input< typeof M124.ServiceConfigSchema >, z.infer< typeof M124.ServiceConfigSchema > >>;
export type Iso808 = Assert<Eq< z.input< typeof M124.ServiceCriticalitySchema >, z.infer< typeof M124.ServiceCriticalitySchema > >>;
export type Iso835 = Assert<Eq< z.input< typeof M124.KernelServiceStatusSchema >, z.infer< typeof M124.KernelServiceStatusSchema > >>;

// system/metadata-persistence.zod.ts
export type Iso809 = Assert<Eq< z.input< typeof M138.MetadataStateSchema >, z.infer< typeof M138.MetadataStateSchema > >>;
Expand DownExpand Up@@ -1622,7 +1623,7 @@ describe('ADR-0122 type-alias convention', () => {
// this title and the section header above the pin list — are now asserted
// against the recomputed count below, so neither can go stale without a red
// test naming it.
it('still declares all 823 isomorphic pins', () => {
it('still declares all 824 isomorphic pins', () => {
// The truth of each pin is proved by tsc, not here — an `Assert<Eq<...>>`
// that stops holding is a compile error with the alias named. What tsc
// cannot notice is a pin that was DELETED: removing the assertion removes
Expand DownExpand Up@@ -1765,9 +1766,28 @@ describe('ADR-0122 type-alias convention', () => {
// A DECREASE, and the first here — the id is retired in place rather than
// renumbered, because the ids are claims about pins and not positions
// (`Iso824` remains the highest, and the next author still takes 825).
//
// 823 -> 824 is #6604 — the `-1` in #4593's arithmetic above, collected.
// That subtraction was not a measurement but an OPEN QUESTION: the alias
// for `system/ServiceStatus` was withheld because declaring it would have
// minted the #4411 dual-source trap against `./api`'s discovery health
// enum, and which of the two names was wrong is a rename decision. The
// maintainer made it on 2026-08-08 (Option B): the kernel side is now
// `KernelServiceStatusSchema`, matching its `KernelServiceMapSchema`
// sibling two lines up in the pin list, and the name it vacated stays
// `./api`'s alone. So the alias the -1 deferred exists at last, under a
// different name than the one it was deferred under — and `Iso835`, not
// `Iso809`, is its id, because the ids are claims about pins and not
// positions (same rule the #4914 decrease above records). Isomorphism
// MEASURED the same way as its 73 siblings: the tree is one enum, one
// boolean, one inline `z.enum` and three optional strings/arrays — no
// `.default()`, `.transform()`, `.catch()` or `.pipe()` anywhere — so the
// two shapes coincide and ADR-0122 gives it a pin rather than an
// `XParsed`. The rename itself contributes 0: renaming a schema const
// moves no shape, and this file counts pins, not names.
const self = readFileSync(fileURLToPath(import.meta.url), 'utf8');
const pins = self.match(/^export type Iso\d+ = Assert</gm) ?? [];
expect(pins).toHaveLength(823);
expect(pins).toHaveLength(824);

// The count is stated in PROSE twice as well — this case's title and the
// section header above the pin list — and until #6605 nothing read either
Expand Down
Loading