diff --git a/.changeset/prune-orphan-featureflag-schema.md b/.changeset/prune-orphan-featureflag-schema.md
new file mode 100644
index 0000000000..130fd3a9e9
--- /dev/null
+++ b/.changeset/prune-orphan-featureflag-schema.md
@@ -0,0 +1,26 @@
+---
+"@objectstack/spec": minor
+---
+
+feat(spec)!: remove the orphaned `FeatureFlagSchema` module (`@objectstack/spec/kernel` feature.zod)
+
+Follow-through of the capabilities-descriptor prune (#3605). `kernel/feature.zod.ts`
+(`FeatureStrategy`, `FeatureFlagSchema`, the `FeatureFlag` factory and its
+`FeatureFlag` / `FeatureFlagInput` types) had **zero runtime consumers**, and its
+only protocol home — the static `ObjectStackCapabilities.system.features`
+descriptor — was itself removed as dead in #3605 (no endpoint ever served it).
+The module was a compile-checkable shape with nowhere to go: not authorable
+(`defineStack` has no `features` key; strict parsing strips it), not registered,
+not read by any engine.
+
+**Migration — flags are runtime configuration, not authored metadata:**
+
+| Removed | Live replacement |
+| --- | --- |
+| `FeatureFlagSchema` / `FeatureFlag.create()` rollout documents (strategies, percentage/group conditions) | the `feature_flags` **settings manifest** (`@objectstack/service-settings`, ADR-0007) — org-tunable `ai_enabled` / `beta_*` toggles, env-overridable via `OS_FEATURE_FLAGS_*` |
+| deployment-level capability gating | `PUBLIC_AUTH_FEATURES` registry (`kernel/public-auth-features.ts`) + `requiresFeature` sugar on actions/params (unchanged, live) |
+| runtime capability discovery | `GET /api/v1/discovery` (dynamic, declared === enforced) |
+
+Docs regenerated (`references/kernel/feature.mdx` removed); the platform skill's
+Feature Flags section and the hand-written quick-reference row now point at the
+settings surface; `PROTOCOL_MAP.md` row dropped.
diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx
index dd0a03e601..2b6da1c9ea 100644
--- a/content/docs/getting-started/quick-reference.mdx
+++ b/content/docs/getting-started/quick-reference.mdx
@@ -74,7 +74,6 @@ Plugin architecture, manifests, and kernel runtime.
| **[Service Registry](/docs/references/kernel/service-registry)** | `service-registry.zod.ts` | ServiceRegistryConfig, ServiceMetadata | Service discovery |
| **[Startup Orchestrator](/docs/references/kernel/startup-orchestrator)** | `startup-orchestrator.zod.ts` | StartupOptions, StartupOrchestrationResult | System startup |
| **[Events](/docs/kernel/events)** | `events.zod.ts` | Event, EventBusConfig | System event bus |
-| **[Feature](/docs/references/kernel/feature)** | `feature.zod.ts` | FeatureFlag | Feature flags |
| **[Metadata Loader](/docs/references/kernel/metadata-loader)** | `metadata-loader.zod.ts` | MetadataLoaderContract | Metadata loading |
| **[Package Registry](/docs/references/kernel/package-registry)** | `package-registry.zod.ts` | InstalledPackage, InstallPackageRequest | Package resolution |
diff --git a/content/docs/references/kernel/feature.mdx b/content/docs/references/kernel/feature.mdx
deleted file mode 100644
index d36ea4dec1..0000000000
--- a/content/docs/references/kernel/feature.mdx
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: Feature
-description: Feature protocol schemas
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-Feature Rollout Strategy
-
-
-**Source:** `packages/spec/src/kernel/feature.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { FeatureFlag, FeatureStrategy } from '@objectstack/spec/kernel';
-import type { FeatureFlag, FeatureStrategy } from '@objectstack/spec/kernel';
-
-// Validate data
-const result = FeatureFlag.parse(data);
-```
-
----
-
-## FeatureFlag
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **name** | `string` | ✅ | Feature key (snake_case) |
-| **label** | `string` | optional | Display label |
-| **description** | `string` | optional | |
-| **enabled** | `boolean` | optional | Is globally enabled |
-| **strategy** | `Enum<'boolean' \| 'percentage' \| 'user_list' \| 'group' \| 'custom'>` | optional | |
-| **conditions** | `{ percentage?: number; users?: string[]; groups?: string[]; expression?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object } }` | optional | |
-| **environment** | `Enum<'dev' \| 'staging' \| 'prod' \| 'all'>` | optional | Environment validity |
-| **expiresAt** | `string` | optional | Feature flag expiration date |
-
-
----
-
-## FeatureStrategy
-
-### Allowed Values
-
-* `boolean`
-* `percentage`
-* `user_list`
-* `group`
-* `custom`
-
-
----
-
diff --git a/content/docs/references/kernel/index.mdx b/content/docs/references/kernel/index.mdx
index e0d642a05c..d0576bd6d2 100644
--- a/content/docs/references/kernel/index.mdx
+++ b/content/docs/references/kernel/index.mdx
@@ -12,7 +12,6 @@ This section contains all protocol schemas for the kernel layer of ObjectStack.
-
diff --git a/content/docs/references/kernel/meta.json b/content/docs/references/kernel/meta.json
index 21bcfe12cf..89f77f1f99 100644
--- a/content/docs/references/kernel/meta.json
+++ b/content/docs/references/kernel/meta.json
@@ -27,7 +27,6 @@
"context",
"dev-plugin",
"execution-context",
- "feature",
"metadata-customization",
"metadata-loader",
"metadata-persistence",
diff --git a/packages/qa/dogfood/test/expression-conformance.ledger.ts b/packages/qa/dogfood/test/expression-conformance.ledger.ts
index 683daad319..f00de776f2 100644
--- a/packages/qa/dogfood/test/expression-conformance.ledger.ts
+++ b/packages/qa/dogfood/test/expression-conformance.ledger.ts
@@ -80,13 +80,15 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
},
{
id: 'cel-formula',
- summary: 'computed / formula field + mapping / feature expressions',
+ summary: 'computed / formula field + mapping expressions',
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
enforcement: '@objectstack/formula celEngine (interpret)',
+ // kernel/feature.zod.ts:expression was covered here until the orphaned
+ // FeatureFlagSchema module was removed (zero runtime consumers once its
+ // capabilities-descriptor home went, #3605).
covers: [
'data/field.zod.ts:expression',
'shared/mapping.zod.ts:expression',
- 'kernel/feature.zod.ts:expression',
],
},
{
diff --git a/packages/spec/PROTOCOL_MAP.md b/packages/spec/PROTOCOL_MAP.md
index 7021262422..94001cd5c1 100644
--- a/packages/spec/PROTOCOL_MAP.md
+++ b/packages/spec/PROTOCOL_MAP.md
@@ -192,7 +192,6 @@ This document serves as the **Grand Map** of the ObjectStack specification. It l
| [`manifest.zod.ts`](src/kernel/manifest.zod.ts) | ⭐ | **Manifest**. The `package.json` equivalent for the stack. |
| [`context.zod.ts`](src/kernel/context.zod.ts) | | **Execution Context**. Request-scoped context (user, transaction). |
| [`events.zod.ts`](src/kernel/events.zod.ts) | | **Kernel Events**. System lifecycle events. |
-| [`feature.zod.ts`](src/kernel/feature.zod.ts) | | **Feature Flags**. Toggleable system features. |
| [`service-registry.zod.ts`](src/kernel/service-registry.zod.ts) | | **Service Registry**. Internal dependency injection. |
| [`metadata-loader.zod.ts`](src/kernel/metadata-loader.zod.ts) | | **Loader**. Logic for loading definitions from disk/DB. |
| [`plugin-loading.zod.ts`](src/kernel/plugin-loading.zod.ts) | ⭐ | **Plugin Loading**. Loading strategies, production-safe hot reload (`environment`, `productionSafety`), full plugin sandboxing (`scope`, `ipc`), code splitting, caching, and performance monitoring. |
diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json
index 71399a0581..3d22378ed5 100644
--- a/packages/spec/api-surface.json
+++ b/packages/spec/api-surface.json
@@ -1419,10 +1419,6 @@
"ExecutionContextSchema (const)",
"ExtensionPoint (type)",
"ExtensionPointSchema (const)",
- "FeatureFlag (type)",
- "FeatureFlagInput (type)",
- "FeatureFlagSchema (const)",
- "FeatureStrategy (const)",
"FieldChange (type)",
"FieldChangeSchema (const)",
"GetPackageRequest (type)",
diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json
index 5451bf8d5b..3c4ddd66b6 100644
--- a/packages/spec/json-schema.manifest.json
+++ b/packages/spec/json-schema.manifest.json
@@ -979,8 +979,6 @@
"kernel/EventWebhookConfig",
"kernel/ExecutionContext",
"kernel/ExtensionPoint",
- "kernel/FeatureFlag",
- "kernel/FeatureStrategy",
"kernel/FieldChange",
"kernel/GetPackageRequest",
"kernel/GetPackageResponse",
diff --git a/packages/spec/scripts/build-skill-references.ts b/packages/spec/scripts/build-skill-references.ts
index bb0c715cda..520e90cdfa 100644
--- a/packages/spec/scripts/build-skill-references.ts
+++ b/packages/spec/scripts/build-skill-references.ts
@@ -113,7 +113,6 @@ const SKILL_MAP: Record = {
'kernel/plugin-lifecycle-events.zod.ts',
'kernel/plugin-capability.zod.ts',
'kernel/plugin-loading.zod.ts',
- 'kernel/feature.zod.ts',
'kernel/metadata-plugin.zod.ts',
],
'objectstack-i18n': [
diff --git a/packages/spec/src/kernel/feature.test.ts b/packages/spec/src/kernel/feature.test.ts
deleted file mode 100644
index 332551c0a6..0000000000
--- a/packages/spec/src/kernel/feature.test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- FeatureStrategy,
- FeatureFlagSchema,
- FeatureFlag,
- type FeatureFlag as FeatureFlagType,
-} from './feature.zod';
-
-describe('FeatureStrategy', () => {
- it('should accept valid strategies', () => {
- expect(() => FeatureStrategy.parse('boolean')).not.toThrow();
- expect(() => FeatureStrategy.parse('percentage')).not.toThrow();
- expect(() => FeatureStrategy.parse('user_list')).not.toThrow();
- expect(() => FeatureStrategy.parse('group')).not.toThrow();
- expect(() => FeatureStrategy.parse('custom')).not.toThrow();
- });
-
- it('should reject invalid strategies', () => {
- expect(() => FeatureStrategy.parse('random')).toThrow();
- expect(() => FeatureStrategy.parse('')).toThrow();
- });
-});
-
-describe('FeatureFlagSchema', () => {
- const minimalFlag = {
- name: 'dark_mode',
- };
-
- it('should accept minimal feature flag', () => {
- expect(() => FeatureFlagSchema.parse(minimalFlag)).not.toThrow();
- });
-
- it('should apply default values', () => {
- const parsed = FeatureFlagSchema.parse(minimalFlag);
- expect(parsed.enabled).toBe(false);
- expect(parsed.strategy).toBe('boolean');
- expect(parsed.environment).toBe('all');
- });
-
- it('should accept feature flag with all fields', () => {
- const full: FeatureFlagType = {
- name: 'new_dashboard',
- label: 'New Dashboard',
- description: 'Enables the new dashboard UI',
- enabled: true,
- strategy: 'percentage',
- conditions: {
- percentage: 50,
- users: ['user_1', 'user_2'],
- groups: ['beta_testers'],
- expression: 'user.plan == "pro"',
- },
- environment: 'staging',
- expiresAt: '2025-12-31T23:59:59Z',
- };
-
- const parsed = FeatureFlagSchema.parse(full);
- expect(parsed.label).toBe('New Dashboard');
- expect(parsed.conditions?.percentage).toBe(50);
- expect(parsed.conditions?.users).toHaveLength(2);
- expect(parsed.environment).toBe('staging');
- });
-
- it('should reject invalid name (not snake_case)', () => {
- expect(() => FeatureFlagSchema.parse({ name: 'DarkMode' })).toThrow();
- expect(() => FeatureFlagSchema.parse({ name: 'dark-mode' })).toThrow();
- expect(() => FeatureFlagSchema.parse({ name: '1invalid' })).toThrow();
- });
-
- it('should reject name that is too short', () => {
- expect(() => FeatureFlagSchema.parse({ name: 'a' })).toThrow();
- });
-
- it('should accept all environment values', () => {
- const envs = ['dev', 'staging', 'prod', 'all'] as const;
- envs.forEach(environment => {
- const parsed = FeatureFlagSchema.parse({ name: 'test_flag', environment });
- expect(parsed.environment).toBe(environment);
- });
- });
-
- it('should reject invalid environment', () => {
- expect(() => FeatureFlagSchema.parse({
- name: 'test_flag',
- environment: 'local',
- })).toThrow();
- });
-
- it('should reject percentage out of range', () => {
- expect(() => FeatureFlagSchema.parse({
- name: 'test_flag',
- conditions: { percentage: 101 },
- })).toThrow();
-
- expect(() => FeatureFlagSchema.parse({
- name: 'test_flag',
- conditions: { percentage: -1 },
- })).toThrow();
- });
-
- it('should accept percentage at boundaries', () => {
- const zero = FeatureFlagSchema.parse({
- name: 'test_flag',
- conditions: { percentage: 0 },
- });
- expect(zero.conditions?.percentage).toBe(0);
-
- const hundred = FeatureFlagSchema.parse({
- name: 'test_flag',
- conditions: { percentage: 100 },
- });
- expect(hundred.conditions?.percentage).toBe(100);
- });
-
- it('should validate expiresAt as ISO datetime', () => {
- expect(() => FeatureFlagSchema.parse({
- name: 'test_flag',
- expiresAt: 'not-a-date',
- })).toThrow();
-
- expect(() => FeatureFlagSchema.parse({
- name: 'test_flag',
- expiresAt: '2025-06-15T10:00:00Z',
- })).not.toThrow();
- });
-});
-
-describe('FeatureFlag.create', () => {
- it('should return the config object as-is', () => {
- const config = { name: 'my_feature', enabled: true };
- const result = FeatureFlag.create(config);
- expect(result).toEqual(config);
- });
-});
diff --git a/packages/spec/src/kernel/feature.zod.ts b/packages/spec/src/kernel/feature.zod.ts
deleted file mode 100644
index 8fea9b4827..0000000000
--- a/packages/spec/src/kernel/feature.zod.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import { SnakeCaseIdentifierSchema } from '../shared/identifiers.zod';
-import { ExpressionInputSchema } from '../shared/expression.zod';
-
-/**
- * Feature Rollout Strategy
- */
-import { lazySchema } from '../shared/lazy-schema';
-export const FeatureStrategy = z.enum([
- 'boolean', // Simple On/Off
- 'percentage', // Gradual rollout (0-100%)
- 'user_list', // Specific users
- 'group', // Specific groups/roles
- 'custom' // Custom constraint/script
-]);
-
-/**
- * Feature Flag Protocol
- *
- * Manages feature toggles and gradual rollouts.
- * Used for CI/CD, A/B Testing, and Trunk-Based Development.
- */
-export const FeatureFlagSchema = lazySchema(() => z.object({
- name: SnakeCaseIdentifierSchema.describe('Feature key (snake_case)'),
- label: z.string().optional().describe('Display label'),
- description: z.string().optional(),
-
- /** Default state */
- enabled: z.boolean().default(false).describe('Is globally enabled'),
-
- /** Rollout Strategy */
- strategy: FeatureStrategy.default('boolean'),
-
- /** Strategy Configuration */
- conditions: z.object({
- percentage: z.number().min(0).max(100).optional(),
- users: z.array(z.string()).optional(),
- groups: z.array(z.string()).optional(),
- expression: ExpressionInputSchema.optional().describe('Custom predicate (CEL).')
- }).optional(),
-
- /** Integration */
- environment: z.enum(['dev', 'staging', 'prod', 'all']).default('all')
- .describe('Environment validity'),
-
- /** Expiration */
- expiresAt: z.string().datetime().optional().describe('Feature flag expiration date'),
-}));
-
-export const FeatureFlag = Object.assign(FeatureFlagSchema, {
- create: >(config: T) => config,
-});
-
-export type FeatureFlag = z.infer;
-export type FeatureFlagInput = z.input;
diff --git a/packages/spec/src/kernel/index.ts b/packages/spec/src/kernel/index.ts
index 9ad3186a3f..9eb70a630b 100644
--- a/packages/spec/src/kernel/index.ts
+++ b/packages/spec/src/kernel/index.ts
@@ -6,7 +6,12 @@ export * from './context.zod';
export * from './dependency-resolution.zod';
export * from './dev-plugin.zod';
export * from './events.zod';
-export * from './feature.zod';
+// feature.zod (FeatureFlagSchema / FeatureStrategy / FeatureFlag factory) was
+// REMOVED per ADR-0056 D8: zero runtime consumers, and its only protocol home —
+// the static ObjectStackCapabilities.system.features descriptor — was itself
+// removed as dead (#3605). Live toggles are the `feature_flags` settings
+// manifest (ADR-0007, env-overridable via OS_FEATURE_FLAGS_*) and the
+// PUBLIC_AUTH_FEATURES registry (public-auth-features.ts) for auth gates.
export * from './manifest.zod';
export * from './metadata-customization.zod';
export * from './namespace-prefix';
diff --git a/packages/spec/src/kernel/public-auth-features.ts b/packages/spec/src/kernel/public-auth-features.ts
index abf5dffcdf..b541888fed 100644
--- a/packages/spec/src/kernel/public-auth-features.ts
+++ b/packages/spec/src/kernel/public-auth-features.ts
@@ -18,10 +18,12 @@ import type { z } from 'zod';
* where each flag's consumption surface, default semantics, and gated spec
* inputs (or exemption rationale) are recorded and CI-enforced.
*
- * NOT to be confused with `kernel/feature.zod.ts` (`FeatureFlagSchema`) —
- * that schema models tenant-scoped runtime rollout toggles (strategies,
- * expiry, conditions). This registry classifies the fixed, deployment-level
- * capability flags that plugin-auth advertises to anonymous clients.
+ * NOT to be confused with runtime rollout toggles — those live on the
+ * `feature_flags` settings manifest (ADR-0007), not in the spec (the former
+ * `kernel/feature.zod.ts` `FeatureFlagSchema` was removed as an orphan: zero
+ * runtime consumers once its dead capabilities-descriptor home went, #3605).
+ * This registry classifies the fixed, deployment-level capability flags that
+ * plugin-auth advertises to anonymous clients.
*
* Consumers:
* - `ui/action.zod.ts` — `requiresFeature` sugar on actions/params is lowered
diff --git a/skills/objectstack-platform/SKILL.md b/skills/objectstack-platform/SKILL.md
index 5fcdc83d0f..7f9b18b624 100644
--- a/skills/objectstack-platform/SKILL.md
+++ b/skills/objectstack-platform/SKILL.md
@@ -1050,43 +1050,21 @@ const metrics = kernel.getPluginMetrics();
## Feature Flags
-Feature flags are a **protocol shape, not a config key**. `FeatureFlagSchema` (from
-`@objectstack/spec/kernel`) defines the flag document. There is **no `featureFlags:` /
-`features:` key on `defineStack`** — strict parsing silently strips unknown keys, so such
-config would be a no-op. (The static `ObjectStackCapabilities.system.features` descriptor
-that once carried flags was removed with the dead capabilities-descriptor cluster — no
-endpoint ever served it; runtime capability discovery is `GET /api/v1/discovery`.)
-
-
-```typescript
-import { FeatureFlag } from '@objectstack/spec/kernel';
-
-// FeatureFlag.create() gives you a compile-checked flag value:
-const aiCopilot = FeatureFlag.create({
- name: 'experimental_ai_copilot',
- label: 'AI Copilot',
- enabled: true,
- strategy: 'percentage',
- conditions: { percentage: 25 }, // 25% of users
- environment: 'prod', // 'dev' | 'staging' | 'prod' | 'all' (default 'all')
-});
-
-const betaKanban = FeatureFlag.create({
- name: 'beta_kanban_view',
- label: 'Kanban View',
- enabled: true,
- strategy: 'group',
- conditions: { groups: ['beta_testers'] },
- environment: 'all',
-});
-```
-
-Strategies: `boolean` | `percentage` | `user_list` | `group` | `custom`
-
-Looking for live, resolvable toggles today? Those are different surfaces, not
-`FeatureFlagSchema`: the `feature_flags` settings manifest
-(`@objectstack/service-settings`, env-overridable via `OS_FEATURE_FLAGS_*`) and auth
-capability gates (`requiresFeature` → `PUBLIC_AUTH_FEATURES`).
+Feature flags are **not a spec/metadata concept**. There is no `featureFlags:` /
+`features:` key on `defineStack` (strict parsing silently strips unknown keys), and the
+former `FeatureFlagSchema` (`@objectstack/spec/kernel`) was removed — it had zero runtime
+consumers, and its only protocol home (the static `ObjectStackCapabilities.system.features`
+descriptor) was itself dead: no endpoint ever served it. Runtime capability discovery is
+`GET /api/v1/discovery`.
+
+The live toggle surfaces are runtime configuration, not authored metadata:
+
+- **`feature_flags` settings manifest** (`@objectstack/service-settings`) — org-tunable
+ toggles like `ai_enabled` / `beta_*`, resolvable at runtime and env-overridable via
+ `OS_FEATURE_FLAGS_*` (ADR-0007 settings cascade).
+- **Auth capability gates** — `requiresFeature` on actions/params lowers to the
+ `PUBLIC_AUTH_FEATURES` registry (`kernel/public-auth-features.ts`), the fixed
+ deployment-level flags plugin-auth advertises to anonymous clients.
---
---
diff --git a/skills/objectstack-platform/references/_index.md b/skills/objectstack-platform/references/_index.md
index a4226f9376..b39cd4171f 100644
--- a/skills/objectstack-platform/references/_index.md
+++ b/skills/objectstack-platform/references/_index.md
@@ -12,7 +12,6 @@ from `node_modules` — there is no local copy in the skill bundle.
- `node_modules/@objectstack/spec/src/data/datasource.zod.ts` — Driver Identifier
- `node_modules/@objectstack/spec/src/data/seed.zod.ts` — Seed Import Strategy
- `node_modules/@objectstack/spec/src/kernel/context.zod.ts` — Runtime Mode Enum
-- `node_modules/@objectstack/spec/src/kernel/feature.zod.ts` — Feature Rollout Strategy
- `node_modules/@objectstack/spec/src/kernel/manifest.zod.ts` — Structured permission grants requested by a plugin (ADR-0025 §3.2).
- `node_modules/@objectstack/spec/src/kernel/metadata-plugin.zod.ts` — Metadata Plugin Protocol
- `node_modules/@objectstack/spec/src/kernel/plugin-capability.zod.ts` — Plugin Capability Protocol