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
56 changes: 56 additions & 0 deletions .changeset/cli-command-contribution-retired.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
---
"@objectstack/spec": minor
---

feat(spec): retire the orphan `CLICommandContributionSchema` export — the manifest surface it described is a tombstone (#12007, ADR-0049)

<!-- adr-0087: registered cli-command-contribution-retired -->

**BREAKING** export removal, landing after the v17.0.0 cut (the lockstep
launch-window convention ships it as `minor`; the prescription is registered
under protocol major 18 — `RETIRED_DEFS_BY_MAJOR[18]`
(`kernel/CLICommandContribution`) + the D3 semantic entry
`cli-command-contribution-retired` — where `os migrate meta` users will
look).

`CLICommandContributionSchema` described a "CLI Command Contribution
declaration in the manifest" and claimed to be "retained for backward
compatibility and for describing command metadata in plugin manifests" — but
after #10724 tombstoned `manifest.contributes.commands`, no manifest surface
could legally carry these entries: the exported schema advertised a shape
whose only declared carrier rejects it. The manifest never referenced this
schema even before the tombstone (its inline `commands` item schema was an
independent duplicate), and it had zero consumers outside spec's own test and
generated artifacts, measured at the retirement's base commit with positive
controls in objectstack, objectui (pinned sha) and cloud — the exported
orphan-value-schema class (#3950: an exported schema with no consumer reads
as a capability).

FROM → TO:

- `CLICommandContributionSchema` / `CLICommandContribution` → *(removed — no
declarative replacement, because no declarative surface ever carried it)*.
CLI commands are registered through oclif's native plugin discovery: the
plugin package declares an `oclif` section in its own `package.json` —
`OclifPluginConfigSchema` / `OclifPluginConfig` (same module) describe that
live surface and survive unchanged.

One-line fix: delete the import (nothing ever read the declaration); if you
describe a plugin's CLI commands, declare the `oclif` section in the plugin's
`package.json` — `OclifPluginConfigSchema` validates it.

The retirement kit:

- whole-def deletion (route 3 — no carrier key, no authored document, so no
tombstone and no D2 conversion; the #11825 / #8715 shape):
`kernel/CLICommandContribution` in `RETIRED_DEFS_BY_MAJOR[18]`, plus the D3
semantic entry `cli-command-contribution-retired`
- pin test (`kernel/cli-command-contribution-retirement.test.ts`): zero
holders for both retired names on every public entry, survivors pinned
(`OclifPluginConfigSchema` / `OclifPluginConfig` — the live `package.json`
`oclif` surface)
- the module docblock's Commander.js migration prose is KEPT — it is cited by
the `contributes.commands` tombstone (full-file deletion was explicitly not
the shape)
- zero authored occurrences in objectstack, objectui or cloud (measured at
dispatch, re-verified at claim), so no in-repo source changes ride along
10 changes: 5 additions & 5 deletions content/docs/references/index.mdx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
---
title: Protocol Reference
description: Every schema published by @objectstack/spec — 1585 schemas across 14 protocol modules
description: Every schema published by @objectstack/spec — 1584 schemas across 14 protocol modules
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All@@ -26,14 +26,14 @@ counts are sums of the rows they head. Regenerate with
| [Data Protocol](/docs/references/data) | 29 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
| [Integration Protocol](/docs/references/integration) | 1 | 27 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
| [Kernel Protocol](/docs/references/kernel) | 31 | 172 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [Kernel Protocol](/docs/references/kernel) | 31 | 171 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
| [Security Protocol](/docs/references/security) | 5 | 27 | Permission sets, row-level security, sharing rules, tenancy posture. |
| [Shared Protocol](/docs/references/shared) | 8 | 32 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 36 | 288 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 152 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
| **Total** | **199** | **1585** | 14 protocol modules |
| **Total** | **199** | **1584** | 14 protocol modules |

---

Expand DownExpand Up@@ -215,13 +215,13 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo

## Kernel Protocol

**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **31 pages, 172 schemas**
**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **31 pages, 171 schemas**

Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry.

| File | Schemas |
| :--- | :--- |
| [`cli-extension.zod.ts`](/docs/references/kernel/cli-extension) | `CLICommandContribution`, `OclifPluginConfig` |
| [`cli-extension.zod.ts`](/docs/references/kernel/cli-extension) | `OclifPluginConfig` |
| [`cluster.zod.ts`](/docs/references/kernel/cluster) | `ClusterCapabilityConfig`, `ClusterDriver`, `ClusterTenantIsolation`, `EventClusterOptions`, `EventDeliverySemantics`, `EventScope`, `MetadataChangeOperation`, `ServiceClusterAnnotations`, `ServiceClusterScope`, `ServiceLeaderStrategy` |
| [`context.zod.ts`](/docs/references/kernel/context) | `KernelContext`, `PreviewModeConfig`, `RuntimeMode`, `TenantRuntimeContext` |
| [`dependency-resolution.zod.ts`](/docs/references/kernel/dependency-resolution) | `DependencyResolutionResult`, `DependencyStatusEnum`, `RequiredAction`, `ResolvedDependency` |
Expand Down
19 changes: 3 additions & 16 deletions content/docs/references/kernel/cli-extension.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -71,26 +71,13 @@ The `objectstack.config.ts` plugins array no longer determines CLI commands.
## TypeScript Usage

```typescript
import { CLICommandContributionSchema, OclifPluginConfigSchema } from '@objectstack/spec/kernel';
import type { CLICommandContribution, OclifPluginConfig } from '@objectstack/spec/kernel';
import { OclifPluginConfigSchema } from '@objectstack/spec/kernel';
import type { OclifPluginConfig } from '@objectstack/spec/kernel';

// Validate data
const result = CLICommandContributionSchema.parse(data);
const result = OclifPluginConfigSchema.parse(data);
```

---

## CLICommandContribution

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **name** | `string` | ✅ | CLI command name |
| **description** | `string` | optional | Command description for help text |
| **module** | `string` | optional | Module path exporting oclif Command classes |


---

## OclifPluginConfig
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -261,7 +261,7 @@ directory rather than per file.
| `cloud/` | 83 |
| `identity/` | 32 |
| `integration/` | 10 |
| `kernel/` | 273 |
| `kernel/` | 272 |
| `qa/` | 6 |
| `shared/` | 20 |
| `system/` | 361 |
2 changes: 0 additions & 2 deletions packages/spec/api-surface/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,6 @@
"BreakingChange (type)",
"BreakingChangeParsed (type)",
"BreakingChangeSchema (const)",
"CLICommandContribution (type)",
"CLICommandContributionSchema (const)",
"CONSUMER_INSTALLABLE_TYPES (const)",
"CORE_PLUGIN_TYPES (const)",
"CapabilityClassification (interface)",
Expand Down
3 changes: 0 additions & 3 deletions packages/spec/authorable-surface/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,9 +20,6 @@
"kernel/BreakingChange:removedIn",
"kernel/BreakingChange:severity",
"kernel/BreakingChange:type",
"kernel/CLICommandContribution:description",
"kernel/CLICommandContribution:module",
"kernel/CLICommandContribution:name",
"kernel/ClusterCapabilityConfig:driver",
"kernel/ClusterCapabilityConfig:driverOptions",
"kernel/ClusterCapabilityConfig:heartbeatMs",
Expand Down
2 changes: 0 additions & 2 deletions packages/spec/export-origins/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,8 +13,6 @@
"BreakingChange": "src/kernel/plugin-versioning.zod.ts#BreakingChange (type)",
"BreakingChangeParsed": "src/kernel/plugin-versioning.zod.ts#BreakingChangeParsed (type)",
"BreakingChangeSchema": "src/kernel/plugin-versioning.zod.ts#BreakingChangeSchema (const)",
"CLICommandContribution": "src/kernel/cli-extension.zod.ts#CLICommandContribution (type)",
"CLICommandContributionSchema": "src/kernel/cli-extension.zod.ts#CLICommandContributionSchema (const)",
"CONSUMER_INSTALLABLE_TYPES": "src/kernel/plugin.zod.ts#CONSUMER_INSTALLABLE_TYPES (const)",
"CORE_PLUGIN_TYPES": "src/kernel/plugin.zod.ts#CORE_PLUGIN_TYPES (const)",
"CapabilityClassification": "src/kernel/platform-capabilities.ts#CapabilityClassification (interface)",
Expand Down
1 change: 0 additions & 1 deletion packages/spec/json-schema.manifest/kernel.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,6 @@
"kernel/ArtifactFileEntry",
"kernel/ArtifactSignature",
"kernel/BreakingChange",
"kernel/CLICommandContribution",
"kernel/CapabilityConformanceLevel",
"kernel/ClusterCapabilityConfig",
"kernel/ClusterDriver",
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';

import {
EXPORT_ENTRY_POINTS,
exportNamesOf,
holdersOf,
} from '../../scripts/lib/export-origins-testkit';

// ─── [#12007] kernel/ CLICommandContribution is RETIRED ─────────────────────
//
// ADR-0049 enforce-or-remove. `kernel/cli-extension.zod.ts` no longer declares
// `CLICommandContributionSchema` / `CLICommandContribution` — 1 emitted def,
// 2 exported names, 3 authorable-surface keys (`name`, `description`,
// `module`), the reference page's section with them.
//
// The measurement that decided it (re-verified at this retirement's base
// commit, 146f448a5, with positive controls — the full record lives in the
// retirement block inside the zod module):
//
// 1. CARRIER — after #10724, `manifest.contributes.commands` is a
// `retiredKey()` tombstone: no manifest surface could legally carry a
// command-contribution entry, so the exported schema advertised a shape
// whose only declared carrier rejects it. The manifest never referenced
// this schema even before the tombstone — its inline `commands` item
// schema was an independent duplicate.
// 2. STATIC — zero readers outside `packages/spec`'s own test and generated
// artifacts, in objectstack, objectui (at the pinned sha) and cloud
// (controls: `OclifPluginConfigSchema` and `@objectstack/spec` both
// resolve hits — the scans see real readers).
// 3. DOORS — no metadata-type binding, no stack collection, no manifest
// embed: no authored document could ever carry it.
//
// Route 3: with no carrier key there is nothing to tombstone and no seam for
// a D2 conversion — `RETIRED_DEFS_BY_MAJOR[18]` plus the D3 semantic entry
// `cli-command-contribution-retired` ARE the declaration.
//
// Form follows #11825 / #8715 / #4988: resolved symbol identity over every
// public entry via the build-time `export-origins/` artifact.
describe('[#12007] kernel/ CLICommandContribution retirement', () => {
/** The 2 names the retired def exported (1 schema const + 1 type). */
const RETIRED_NAMES = [
'CLICommandContributionSchema',
'CLICommandContribution',
] as const;

/**
* Names that must SURVIVE on `./kernel`: the LIVE half of the same module —
* `OclifPluginConfigSchema` describes the `oclif` section of a plugin's own
* `package.json`, the mechanism that actually registers CLI commands.
* Exactly what a too-wide "tidy the cli-extension module" sweep would take
* (full-file deletion was explicitly NOT the shape: the module docblock's
* Commander.js migration prose is cited by the `contributes.commands`
* tombstone).
*/
const MUST_SURVIVE_KERNEL = [
'OclifPluginConfigSchema',
'OclifPluginConfig',
] as const;

it('every retired name has ZERO holders on any public entry; the survivors still stand', () => {
// Anti-vacuity: the baseline must cover the real surface.
for (const needed of ['.', './kernel']) {
expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed);
}
expect(
exportNamesOf('./kernel').length,
'./kernel must export a non-trivial surface'
).toBeGreaterThan(50);

// ── ABSENCE (every entry, not just ./kernel) ──────────────────────────
for (const name of RETIRED_NAMES) {
expect(holdersOf(name), `${name} must have zero holders after #12007`).toEqual([]);
}

// ── SURVIVAL ──────────────────────────────────────────────────────────
const kernelNames = exportNamesOf('./kernel');
for (const name of MUST_SURVIVE_KERNEL) {
expect(kernelNames, `${name} must SURVIVE this retirement`).toContain(name);
}
});

it('the runtime barrel resolves without the retired schema and keeps the survivor', async () => {
const kernel = await import('./index');
expect(kernel, 'CLICommandContributionSchema must not be exported after #12007')
.not.toHaveProperty('CLICommandContributionSchema');
// Anti-vacuity: the barrel really resolved and still exports the live
// oclif surface plus an unrelated kernel anchor.
expect(kernel).toHaveProperty('OclifPluginConfigSchema');
expect(kernel).toHaveProperty('ManifestSchema');
});
});
68 changes: 4 additions & 64 deletions packages/spec/src/kernel/cli-extension.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,9 @@
import { describe, it, expect } from 'vitest';
import {
CLICommandContributionSchema,
OclifPluginConfigSchema,
} from './cli-extension.zod';

describe('CLICommandContributionSchema', () => {
it('should accept valid command contribution', () => {
const result = CLICommandContributionSchema.parse({
name: 'marketplace',
description: 'Manage marketplace applications',
});
expect(result.name).toBe('marketplace');
expect(result.description).toBe('Manage marketplace applications');
});

it('should accept command with module path', () => {
const result = CLICommandContributionSchema.parse({
name: 'deploy',
description: 'Deploy to cloud',
module: './dist/commands/deploy.js',
});
expect(result.module).toBe('./dist/commands/deploy.js');
});

it('should accept minimal command (name only)', () => {
const result = CLICommandContributionSchema.parse({
name: 'sync',
});
expect(result.name).toBe('sync');
expect(result.description).toBeUndefined();
expect(result.module).toBeUndefined();
});

it('should accept hyphenated command names', () => {
const result = CLICommandContributionSchema.parse({
name: 'cloud-sync',
});
expect(result.name).toBe('cloud-sync');
});

it('should reject invalid command names', () => {
const invalidNames = [
'Uppercase',
'has spaces',
'123start',
'-leading-hyphen',
'special_underscore',
'dot.name',
'',
];

invalidNames.forEach(name => {
expect(() => CLICommandContributionSchema.parse({ name })).toThrow();
});
});

it('should accept valid lowercase alphanumeric names', () => {
const validNames = ['a', 'abc', 'a1', 'my-command', 'plugin2'];

validNames.forEach(name => {
expect(() => CLICommandContributionSchema.parse({ name })).not.toThrow();
});
});
});
import { OclifPluginConfigSchema } from './cli-extension.zod';

// [#12007] The `CLICommandContributionSchema` block that used to sit here left
// with the retired export (ADR-0049 enforce-or-remove) — the rejection/holder
// pins live in `cli-command-contribution-retirement.test.ts`.
describe('OclifPluginConfigSchema', () => {
it('should accept valid oclif plugin config', () => {
const result = OclifPluginConfigSchema.parse({
Expand Down
Loading
Loading