diff --git a/.changeset/25608-retire-package-upgrade.md b/.changeset/25608-retire-package-upgrade.md
new file mode 100644
index 00000000000..505fbcceb2c
--- /dev/null
+++ b/.changeset/25608-retire-package-upgrade.md
@@ -0,0 +1,35 @@
+---
+'@objectstack/spec': minor
+---
+
+**BREAKING** — the package upgrade model (`kernel/package-upgrade.zod.ts`: upgrade plan, metadata diff, impact level, pre-upgrade snapshot, upgrade and rollback request/response, phase vocabulary), the four `IPackageService` upgrade members and the `plan` member of `PackageUpgradeResponse` leave the public surface (#25608).
+
+A `major`-class change, recorded as `minor` under the launch-window convention. Maintainer ruling A (`6259332195`, item 3, 「其他同意」): "**Retired:** `packages/spec/src/kernel/package-upgrade.zod.ts` (plan, diff, conflict, snapshot, rollback; zero runtime consumers), with its ADR-0087 conversion." Done directly in 18.x, with no retirement stage; ADR-0049 enforce-or-remove.
+
+Clause-②: no (narrowing)
+Metadata-protocol: narrowed
+Ruling-ref: 6259332195
+
+**Why.** `@objectstack/spec` declared a complete package upgrade lifecycle — plan the upgrade against a metadata diff, grade its impact, take a pre-upgrade snapshot, three-way merge the customer's customizations, roll back to the snapshot — and nothing on the platform implemented any of it. No provider implements `IPackageService`, so `planUpgrade`, `upgrade`, `rollback` and `getSnapshot` had no body anywhere; no route answers `UpgradePackageRequest` or `RollbackPackageRequest`; and no package, example, app or skill in this repository, in objectui or in hotcrm read any of the names. `UpgradePackageRequest.createSnapshot: true` and `mergeStrategy: 'three-way-merge'` read as protections the platform gave an operator's customizations, and it never did.
+
+### FROM → TO
+
+| removed | from | what to write instead |
+| --- | --- | --- |
+| `UpgradePlanSchema`, `MetadataDiffItemSchema`, `MetadataChangeTypeSchema`, `UpgradeImpactLevelSchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/kernel` | nothing — no code ever generated an upgrade plan or diffed two package versions. |
+| `UpgradeSnapshotSchema`, `RollbackPackageRequestSchema`, `RollbackPackageResponseSchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/kernel` | nothing — no snapshot was ever taken. The package rollback the platform serves is the commit rollback, `POST /api/v1/packages/:id/rollback` with a `commitId`, answered by `RollbackToPackageCommitResponseSchema` (`@objectstack/spec/api`). |
+| `UpgradePackageRequestSchema`, `UpgradePackageResponseSchema`, `UpgradePhaseSchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/kernel` | nothing — no upgrade ever ran. Installing a new version over an installed package is a re-install: `POST /api/v1/packages` with `overwrite: true` (the SDK's `packages.install(manifest, { overwrite: true })`). |
+| `IPackageService.planUpgrade`, `.upgrade`, `.rollback`, `.getSnapshot`, and their inputs `PlanUpgradeInput`, `ExecuteUpgradeInput`, `RollbackInput` | `@objectstack/spec/contracts` | delete the members from your implementation and the imports of the three inputs — the contract no longer carries them. |
+| `PackageUpgradeResponse.data.plan` | `@objectstack/spec/api` | nothing — it carried the retired `UpgradePlan`. `PackageUpgradeRequestSchema` and `PackageUpgradeResponseSchema` themselves stay published, bound to no route. |
+
+**The one-line fix: delete every import of the names above and every read of `PackageUpgradeResponse.data.plan`.** The compiler finds them (`TS2305: Module '"@objectstack/spec/kernel"' has no exported member …`, and `TS2339` for `plan`). No behaviour is lost — nothing ever read or produced these shapes.
+
+**What stays.** The package model beside it — `ManifestSchema`, `InstalledPackageSchema`, `PackageArtifactSchema`, the install, uninstall, enable/disable and dependency-resolution members of `IPackageService` — is unchanged. The `UpgradePlan.estimatedDuration` tombstone leaves with its def's source; its `RETIRED_KEYS_BY_MAJOR[18]` entry and the D3 entry `kernel-package-lifecycle-durations-unit-in-key` stay as history.
+
+⚠️ Runtime behaviour is deliberately **unchanged**: nothing ever parsed a retired shape or called a retired member, so every request answers exactly as before. The removal retracts a false claim, not a capability. **No deprecation window** (startup-stage posture: retirements take effect immediately).
+
+⚠️ **The out-of-repo consumer population beyond objectui and hotcrm is NOT MEASURED.** Inside this repository the names occurred only in their declarations, their own tests, generated artifacts and prose; objectui (main and the pinned `.objectui-sha`) and hotcrm name none of them; `@objectstack/spec` is published, so readers elsewhere were not measured.
+
+The ADR-0087 D3 semantic entry `kernel-package-upgrade-family-retired` carries the judgement, and the ten defs are registered in `RETIRED_DEFS_BY_MAJOR[18]`: none of these shapes is a stack collection or a metadata type, so there is no source for a D2 conversion to rewrite and no carrier key for a tombstone.
+
+
diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx
index 1ea2b087d35..40a06440ab4 100644
--- a/content/docs/getting-started/quick-reference.mdx
+++ b/content/docs/getting-started/quick-reference.mdx
@@ -61,7 +61,7 @@ Presentation layer - views, forms, dashboards, and app branding.
| **[Chart](/docs/references/ui/chart)** | `chart.zod.ts` | Chart, ChartType | Chart definitions |
| **[Widget Contract](/docs/protocol/objectui/widget-contract)** ↗ | `widget.zod.ts` | FieldWidgetProps | Props a custom field widget receives — the contract is documented with ObjectUI, outside `references/ui/` |
-## Kernel Protocol (17 of 30 schemas)
+## Kernel Protocol (17 of 29 schemas)
Plugin architecture, manifests, and kernel runtime.
diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx
index 518793d23d7..3c4db9ead16 100644
--- a/content/docs/references/api/package-api.mdx
+++ b/content/docs/references/api/package-api.mdx
@@ -585,7 +585,7 @@ Upgrade package response
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata |
-| **data** | `{ success: boolean; phase: string; plan?: object; snapshotId?: string; … }` | ✅ | |
+| **data** | `{ success: boolean; phase: string; snapshotId?: string; conflicts?: object[]; … }` | ✅ | |
### Nested Shape: `PackageUpgradeResponse.error`
@@ -616,7 +616,6 @@ Upgrade package response
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Whether the upgrade succeeded |
| **phase** | `string` | ✅ | Current upgrade phase |
-| **plan** | `{ packageId: string; fromVersion: string; toVersion: string; impactLevel: Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>; … }` | optional | Upgrade plan that was executed |
| **snapshotId** | `string` | optional | Snapshot ID for rollback |
| **conflicts** | `{ path: string; baseValue: any; incomingValue: any; customValue: any }[]` | optional | Unresolved merge conflicts |
| **errorMessage** | `string` | optional | Error message if failed |
diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx
index fb20c56a6a1..d98bce42a1d 100644
--- a/content/docs/references/index.mdx
+++ b/content/docs/references/index.mdx
@@ -1,7 +1,7 @@
---
title: Protocol reference — every schema by module
navTitle: Protocol Reference
-description: Every schema published by @objectstack/spec — 1549 schemas across 14 protocol modules
+description: Every schema published by @objectstack/spec — 1539 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/. */}
@@ -26,7 +26,7 @@ counts are sums of the rows they head. Regenerate with
| [Data Protocol](/docs/references/data) | 29 | 178 | 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 | 12 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
-| [Kernel Protocol](/docs/references/kernel) | 30 | 161 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
+| [Kernel Protocol](/docs/references/kernel) | 29 | 151 | Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry. |
| [Marketplace Protocol](/docs/references/marketplace) | 4 | 30 | The package & marketplace format — package identity and versions, listing, publish, review, search, install, template manifests. |
| [QA Protocol](/docs/references/qa) | 1 | 8 | Declarative test suites — scenarios, steps, actions and assertions. |
| [Security Protocol](/docs/references/security) | 5 | 34 | Permission sets, row-level security, sharing rules, tenancy posture. |
@@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
| [System Protocol](/docs/references/system) | 36 | 285 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
| [UI Protocol](/docs/references/ui) | 16 | 169 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
-| **Total** | **198** | **1549** | 14 protocol modules |
+| **Total** | **197** | **1539** | 14 protocol modules |
---
@@ -199,7 +199,7 @@ The single connector protocol (ADR-0097) — catalog descriptors and provider-bo
## Kernel Protocol
-**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **30 pages, 161 schemas**
+**Source:** `packages/spec/src/kernel/` · **Import:** `@objectstack/spec/kernel` · **29 pages, 151 schemas**
Plugin lifecycle and manifests, capabilities and security, metadata loading, service registry.
@@ -222,7 +222,6 @@ Plugin lifecycle and manifests, capabilities and security, metadata loading, ser
| [`metadata-protection.zod.ts`](/docs/references/kernel/metadata-protection) | `MetadataLock`, `MetadataLockSource`, `MetadataProvenance` |
| [`package-artifact.zod.ts`](/docs/references/kernel/package-artifact) | `ArtifactChecksum`, `ArtifactFileEntry`, `ArtifactSignature`, `MetadataCategoryEnum`, `PackageArtifact` |
| [`package-registry.zod.ts`](/docs/references/kernel/package-registry) | `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `GetPackageRequest`, `GetPackageResponse`, `InstallPackageRequest`, `InstallPackageResponse`, `InstalledPackage`, `InstalledPackageSource`, `ListPackagesRequest`, `ListPackagesResponse`, `NamespaceConflictError`, `NamespaceRegistryEntry`, `PackageStatusEnum`, `UninstallPackageRequest`, `UninstallPackageResponse` |
-| [`package-upgrade.zod.ts`](/docs/references/kernel/package-upgrade) | `MetadataChangeType`, `MetadataDiffItem`, `RollbackPackageRequest`, `RollbackPackageResponse`, `UpgradeImpactLevel`, `UpgradePackageRequest`, `UpgradePackageResponse`, `UpgradePhase`, `UpgradePlan`, `UpgradeSnapshot` |
| [`plugin.zod.ts`](/docs/references/kernel/plugin) | `Plugin` |
| [`plugin-capability.zod.ts`](/docs/references/kernel/plugin-capability) | `CapabilityConformanceLevel`, `ExtensionPoint`, `PluginCapability`, `PluginCapabilityManifest`, `PluginDependency`, `PluginInterface`, `ProtocolFeature`, `ProtocolReference`, `ProtocolVersion` |
| [`plugin-lifecycle-advanced.zod.ts`](/docs/references/kernel/plugin-lifecycle-advanced) | `HotReloadConfig`, `PluginHealthCheck`, `PluginHealthReport`, `PluginHealthStatus`, `PluginStateSnapshot` |
diff --git a/content/docs/references/kernel/index.mdx b/content/docs/references/kernel/index.mdx
index 7f8b012e72b..4c1c3e77c5f 100644
--- a/content/docs/references/kernel/index.mdx
+++ b/content/docs/references/kernel/index.mdx
@@ -1,7 +1,7 @@
---
title: Kernel Protocol — complete schema reference
navTitle: Kernel Protocol
-description: "The ObjectStack Kernel Protocol in 30 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
+description: "The ObjectStack Kernel Protocol in 29 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
---
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -26,7 +26,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 048eb7e7e6d..5574501de14 100644
--- a/content/docs/references/kernel/meta.json
+++ b/content/docs/references/kernel/meta.json
@@ -18,7 +18,6 @@
"---Packages---",
"package-artifact",
"package-registry",
- "package-upgrade",
"---Metadata & Runtime---",
"cli-extension",
"cluster",
diff --git a/content/docs/references/kernel/package-upgrade.mdx b/content/docs/references/kernel/package-upgrade.mdx
deleted file mode 100644
index 7ba57ff6eff..00000000000
--- a/content/docs/references/kernel/package-upgrade.mdx
+++ /dev/null
@@ -1,319 +0,0 @@
----
-title: Package Upgrade — Kernel Protocol reference
-navTitle: Package Upgrade
-description: "Defines the complete lifecycle for upgrading installed packages, including pre-upgrade analysis, snapshot/backup, execution, validation."
----
-
-{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
-
-## Package Upgrade Protocol
-
-Defines the complete lifecycle for upgrading installed packages,
-including pre-upgrade analysis, snapshot/backup, execution, validation,
-and rollback capabilities.
-
-### Architecture Alignment
-- **Salesforce**: Managed Package upgrade with push upgrades and subscriber control
-- **ServiceNow**: Update Sets with preview, commit, and back-out support
-- **Helm**: Helm upgrade with rollback to previous release
-- **Kubernetes**: Rolling update with readiness probes and automatic rollback
-
-### Upgrade Flow
-```
-1. PreCheck → Validate compatibility, check dependencies
-2. Plan → Generate upgrade plan with metadata diff
-3. Snapshot → Backup current state (metadata + customizations)
-4. Execute → Apply new package metadata with 3-way merge
-5. Validate → Run post-upgrade health checks
-6. Commit → Finalize upgrade (or Rollback on failure)
-```
-
-
-**Source:** `packages/spec/src/kernel/package-upgrade.zod.ts`
-
-
-## TypeScript Usage
-
-```typescript
-import { MetadataChangeTypeSchema, MetadataDiffItemSchema, RollbackPackageRequestSchema, RollbackPackageResponseSchema, UpgradeImpactLevelSchema, UpgradePackageRequestSchema, UpgradePackageResponseSchema, UpgradePhaseSchema, UpgradePlanSchema, UpgradeSnapshotSchema } from '@objectstack/spec/kernel';
-import type { MetadataChangeType, MetadataDiffItem, RollbackPackageRequest, RollbackPackageResponse, UpgradeImpactLevel, UpgradePackageRequest, UpgradePackageResponse, UpgradePhase, UpgradePlan, UpgradeSnapshot } from '@objectstack/spec/kernel';
-
-// Validate data
-const result = MetadataChangeTypeSchema.parse(data);
-```
-
----
-
-## MetadataChangeType
-
-Type of metadata change between package versions
-
-### Allowed Values
-
-* `added`
-* `modified`
-* `removed`
-* `renamed`
-
-
----
-
-## MetadataDiffItem
-
-Single metadata change between package versions
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **type** | `string` | ✅ | Metadata type |
-| **name** | `string` | ✅ | Metadata name |
-| **changeType** | `Enum<'added' \| 'modified' \| 'removed' \| 'renamed'>` | ✅ | Category of metadata modification (added, modified, removed, or renamed) |
-| **hasConflict** | `boolean` | optional (default: `false`) | Whether this change may conflict with customizations |
-| **summary** | `string` | optional | Human-readable change summary |
-| **previousName** | `string` | optional | Previous name if renamed |
-
-
----
-
-## RollbackPackageRequest
-
-Rollback package request
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **packageId** | `string` | ✅ | Package ID to rollback |
-| **snapshotId** | `string` | ✅ | Snapshot ID to restore from |
-| **rollbackCustomizations** | `boolean` | optional (default: `true`) | Whether to restore pre-upgrade customizations |
-
-
----
-
-## RollbackPackageResponse
-
-Rollback package response
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **success** | `boolean` | ✅ | Whether the rollback succeeded |
-| **restoredVersion** | `string` | optional | Version restored to |
-| **message** | `string` | optional | Rollback status message |
-
-
----
-
-## UpgradeImpactLevel
-
-Severity of upgrade impact
-
-### Allowed Values
-
-* `none`
-* `low`
-* `medium`
-* `high`
-* `critical`
-
-
----
-
-## UpgradePackageRequest
-
-Upgrade package request
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **packageId** | `string` | ✅ | Package ID to upgrade |
-| **targetVersion** | `string` | optional | Target version (defaults to latest) |
-| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | optional | New manifest (if installing from local) |
-| **createSnapshot** | `boolean` | optional (default: `true`) | Whether to create a pre-upgrade backup snapshot |
-| **mergeStrategy** | `Enum<'keep-custom' \| 'accept-incoming' \| 'three-way-merge'>` | optional (default: `"three-way-merge"`) | How to handle customer customizations |
-| **dryRun** | `boolean` | optional (default: `false`) | Preview upgrade without making changes |
-| **skipValidation** | `boolean` | optional (default: `false`) | Skip pre-upgrade compatibility checks |
-
-### Nested Shape: `UpgradePackageRequest.manifest`
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) |
-| **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") |
-| **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package |
-| **version** | `string` | ✅ | Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101) |
-| **type** | `Enum<'plugin' \| 'ui' \| 'driver' \| 'server' \| 'app' \| 'theme' \| 'agent' \| 'objectql' \| …>` | ✅ | Type of package |
-| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
-| **installModes** | `Enum<'managed' \| 'unmanaged'>[]` | optional | Install modes this package permits (ADR-0131 D6): managed \| unmanaged. Absent means ['managed']. |
-| **name** | `string` | ✅ | Human-readable package name |
-| **description** | `string` | optional | Package description |
-| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
-| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
-| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
-| **dependencies** | `Record` | optional | Package dependencies |
-| **configuration** | `never` | optional | [REMOVED] `manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
-| **contributes** | `{ kinds?: object[] }` | optional | Platform contributions |
-| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
-| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
-| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
-| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
-| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (`manifest.runtime`) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the `node` tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation. |
-| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
-| **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) |
-| **runtime** | `Enum<'node' \| 'sandbox' \| 'worker'>` | optional | Plugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting `node` → HTTP 422 + manual review); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares |
-| **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) |
-| **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) |
-| **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) |
-
-
----
-
-## UpgradePackageResponse
-
-Upgrade package response
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **success** | `boolean` | ✅ | Whether the upgrade succeeded |
-| **phase** | `Enum<'pending' \| 'analyzing' \| 'snapshot' \| 'executing' \| 'migrating' \| 'validating' \| 'completed' \| 'failed' \| 'rolling-back' \| 'rolled-back'>` | ✅ | Current upgrade phase |
-| **plan** | `{ packageId: string; fromVersion: string; toVersion: string; impactLevel: Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>; … }` | optional | Upgrade plan |
-| **snapshotId** | `string` | optional | Snapshot ID for rollback |
-| **conflicts** | `{ path: string; baseValue: any; incomingValue: any; customValue: any }[]` | optional | Unresolved merge conflicts |
-| **errorMessage** | `string` | optional | Error message if upgrade failed |
-| **message** | `string` | optional | Human-readable status message |
-
-### Nested Shape: `UpgradePackageResponse.plan`
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **packageId** | `string` | ✅ | Package identifier |
-| **fromVersion** | `string` | ✅ | Currently installed version |
-| **toVersion** | `string` | ✅ | Target upgrade version |
-| **impactLevel** | `Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>` | ✅ | Severity assessment from none (seamless) to critical (breaking changes) |
-| **changes** | `{ type: string; name: string; changeType: Enum<'added' \| 'modified' \| 'removed' \| 'renamed'>; hasConflict?: boolean; … }[]` | ✅ | All metadata changes |
-| **affectedCustomizations** | `integer` | optional (default: `0`) | Count of customizations that may be affected |
-| **requiresMigration** | `boolean` | optional (default: `false`) | Whether data migration scripts are needed |
-| **migrationScripts** | `string[]` | optional | Paths to migration scripts |
-| **dependencyUpgrades** | `{ packageId: string; fromVersion: string; toVersion: string }[]` | optional | Dependent packages that also need upgrading |
-| **estimatedDurationSeconds** | `integer` | optional | Estimated upgrade duration in seconds |
-| **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. |
-| **summary** | `string` | optional | Human-readable upgrade summary |
-
-
----
-
-## UpgradePhase
-
-Current phase of the upgrade process
-
-### Allowed Values
-
-* `pending`
-* `analyzing`
-* `snapshot`
-* `executing`
-* `migrating`
-* `validating`
-* `completed`
-* `failed`
-* `rolling-back`
-* `rolled-back`
-
-
----
-
-## UpgradePlan
-
-Upgrade analysis plan generated before execution
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **packageId** | `string` | ✅ | Package identifier |
-| **fromVersion** | `string` | ✅ | Currently installed version |
-| **toVersion** | `string` | ✅ | Target upgrade version |
-| **impactLevel** | `Enum<'none' \| 'low' \| 'medium' \| 'high' \| 'critical'>` | ✅ | Severity assessment from none (seamless) to critical (breaking changes) |
-| **changes** | `{ type: string; name: string; changeType: Enum<'added' \| 'modified' \| 'removed' \| 'renamed'>; hasConflict?: boolean; … }[]` | ✅ | All metadata changes |
-| **affectedCustomizations** | `integer` | optional (default: `0`) | Count of customizations that may be affected |
-| **requiresMigration** | `boolean` | optional (default: `false`) | Whether data migration scripts are needed |
-| **migrationScripts** | `string[]` | optional | Paths to migration scripts |
-| **dependencyUpgrades** | `{ packageId: string; fromVersion: string; toVersion: string }[]` | optional | Dependent packages that also need upgrading |
-| **estimatedDurationSeconds** | `integer` | optional | Estimated upgrade duration in seconds |
-| **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. |
-| **summary** | `string` | optional | Human-readable upgrade summary |
-
-### Nested Shape: `UpgradePlan.changes[number]`
-
-Single metadata change between package versions
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **type** | `string` | ✅ | Metadata type |
-| **name** | `string` | ✅ | Metadata name |
-| **changeType** | `Enum<'added' \| 'modified' \| 'removed' \| 'renamed'>` | ✅ | Category of metadata modification (added, modified, removed, or renamed) |
-| **hasConflict** | `boolean` | optional (default: `false`) | Whether this change may conflict with customizations |
-| **summary** | `string` | optional | Human-readable change summary |
-| **previousName** | `string` | optional | Previous name if renamed |
-
-
----
-
-## UpgradeSnapshot
-
-Pre-upgrade state snapshot for rollback capability
-
-### Properties
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **id** | `string` | ✅ | Snapshot identifier |
-| **packageId** | `string` | ✅ | Package identifier |
-| **fromVersion** | `string` | ✅ | Version before upgrade |
-| **toVersion** | `string` | ✅ | Target upgrade version |
-| **tenantId** | `string` | optional | Tenant identifier |
-| **previousManifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` | ✅ | Complete manifest of the previous package version |
-| **metadataSnapshot** | `{ type: string; name: string; metadata: Record }[]` | ✅ | Snapshot of all package metadata |
-| **customizationSnapshot** | `Record[]` | optional | Snapshot of customer customizations |
-| **createdAt** | `string` | ✅ | Snapshot creation timestamp |
-| **expiresAt** | `string` | optional | Snapshot expiry timestamp |
-
-### Nested Shape: `UpgradeSnapshot.previousManifest`
-
-| Property | Type | Required | Description |
-| :--- | :--- | :--- | :--- |
-| **id** | `string` | ✅ | Unique package identifier — must match reverse-domain notation (e.g. com.acme.crm) |
-| **namespace** | `string` | optional | Short namespace identifier; also the mandatory prefix of every object name (e.g. "todo" → object names "todo_task", "todo_project") |
-| **defaultDatasource** | `string` | optional (default: `"default"`) | Default datasource for all objects in this package |
-| **version** | `string` | ✅ | Package version (SemVer 2.0.0 — e.g. 1.2.3, 2.0.0-beta.1, 1.0.0+20230101) |
-| **type** | `Enum<'plugin' \| 'ui' \| 'driver' \| 'server' \| 'app' \| 'theme' \| 'agent' \| 'objectql' \| …>` | ✅ | Type of package |
-| **scope** | `Enum<'cloud' \| 'system' \| 'project'>` | optional (default: `"project"`) | Deployment scope: cloud \| system \| project |
-| **installModes** | `Enum<'managed' \| 'unmanaged'>[]` | optional | Install modes this package permits (ADR-0131 D6): managed \| unmanaged. Absent means ['managed']. |
-| **name** | `string` | ✅ | Human-readable package name |
-| **description** | `string` | optional | Package description |
-| **permissions** | `{ services?: string[]; hooks?: string[]; network?: string[]; fs?: string[] }` | optional | Required permissions at the AUTHORING stage: the structured plugin block `{ services, hooks, network, fs }` (ADR-0025 §3.2) — the legacy flat string list is retired — and at the assembled stage the same key is the ADR-0090 `PermissionSet[]` collection instead (`AssembledPackageBodySchema`) |
-| **objects** | `string[]` | optional | Glob patterns for ObjectQL schemas files |
-| **datasources** | `string[]` | optional | Glob patterns for Datasource definitions |
-| **dependencies** | `Record` | optional | Package dependencies |
-| **configuration** | `never` | optional | [REMOVED] `manifest.configuration` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read the block: no settings UI rendered it and no loader resolved a setting from it, so authoring it configured nothing. Worse, `properties.*.secret` promised "value is encrypted/masked (e.g. API Keys)" while nothing encrypted, masked or even parsed the flag — a false assurance about credential handling. Delete the key. A plugin is configured by the host that composes it: pass options to its constructor in `defineStack({ plugins: [new MyPlugin({ … })] })`, which is the enforced channel. A declarative settings surface must be designed with an enforcing reader first, not revived here. |
-| **contributes** | `{ kinds?: object[] }` | optional | Platform contributions |
-| **data** | `{ object: string; externalId?: string \| string[]; mode?: Enum<'insert' \| 'update' \| 'upsert' \| 'replace' \| 'ignore'>; env?: Enum<'prod' \| 'dev' \| 'test'>[]; … }[]` | optional | Initial seed data (prefer top-level data field) |
-| **capabilities** | `never` | optional | [REMOVED] `manifest.capabilities` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — no discovery path ever consulted the block: nothing read `implements`, `provides`, `requires`, `extensionPoints` or `extensions`, so the declared "interoperability and automatic discovery" never happened. Delete the key. Real dependency resolution runs off top-level `manifest.dependencies`, which stays. Capability-based discovery must be designed with an enforcing reader first, not revived here. |
-| **extensions** | `never` | optional | [REMOVED] `manifest.extensions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — an untyped map with zero readers: whatever was parked here was stored and never consulted. Delete the key. Extend the platform through the enforced channels instead: `contributes.kinds` registers metadata kinds, `navigationContributions` injects navigation into other packages' apps, and code-level extension happens in the plugin itself (`init`/`start`). |
-| **navigationContributions** | `{ app: string; group?: string; priority?: integer; items: (object \| … +9 more)[] }[]` | optional | Navigation items this package contributes into apps owned by other packages |
-| **loading** | `never` | optional | [REMOVED] `manifest.loading` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — the entire block (`strategy`, `preload`, `codeSplitting`, `dynamicImport`, `initialization`, `dependencyResolution`, `hotReload`, `caching`, `sandboxing`, `monitoring`) had no runtime reader in any repo, so authoring it configured nothing. Delete the key. Plugins are composed at boot — `defineStack` registers them and the kernel runs `init` then `start` in an order topologically resolved from each composed plugin's own `dependencies` / `optionalDependencies` (`resolvePluginOrder`); the set is fixed until the process restarts. ⚠️ `loading.sandboxing` in particular never isolated anything: it did not run plugins in a process, vm, iframe or web-worker, and `allowedServices` gated no call. If you were relying on it for isolation, you had none — and the plugin trust tier (`manifest.runtime`) does not give it back: that tier is enforced at the cloud marketplace PUBLISH gate only (an unverified publisher requesting the `node` tier is rejected with HTTP 422 and forced to manual review), while load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares. ⛔ Nor do the permission declarations give it back: the install-time granted set is REGISTERED on the PluginPermissionEnforcer at load and queried by nothing, so it refuses no operation. Neither surface confines a plugin today — do not author either one expecting isolation. |
-| **engine** | `{ objectstack: string }` | optional | Platform compatibility requirements (legacy; superseded by `engines`) |
-| **engines** | `{ platform?: string; protocol?: string }` | optional | Plugin compatibility ranges (ADR-0025 §3.2; supersedes `engine`) |
-| **runtime** | `Enum<'node' \| 'sandbox' \| 'worker'>` | optional | Plugin trust tier the plugin declares (ADR-0025 §3.6) — enforced at the cloud marketplace publish gate (unverified publisher requesting `node` → HTTP 422 + manual review); load-side enforcement is NOT implemented, so a locally installed plugin is not isolated by the tier it declares |
-| **packaging** | `Enum<'bundled' \| 'manifest-deps'>` | optional | Dependency packaging strategy (ADR-0025 §3.3) |
-| **main** | `string` | optional | Entry module of a code-bearing plugin, relative to the plugin root; `os plugin build` bundles it and writes `dist/index.mjs` here in the compiled manifest (ADR-0025 §3.4) |
-| **integrity** | `Record` | optional | Per-file content digests of the plugin artifact (ADR-0025 §3.2) |
-
-
----
-
diff --git a/content/docs/upgrading.mdx b/content/docs/upgrading.mdx
index 1c8a8ec8618..ba3a61a145e 100644
--- a/content/docs/upgrading.mdx
+++ b/content/docs/upgrading.mdx
@@ -610,8 +610,10 @@ and the deprecation window a property must sit through before it can be removed
## Not this page
**Upgrading an installed package** — a template app or a third-party package
-already installed into a deployment — is a different operation with its own
-lifecycle (pre-check, plan, snapshot, execute, validate, commit or roll back).
-It is neither of the two halves above: it moves *someone else's* metadata inside
-your deployment. See the [package upgrade
-protocol](/docs/references/kernel/package-upgrade).
+already installed into a deployment — is a different operation. It is neither of
+the two halves above: it moves *someone else's* metadata inside your deployment.
+The platform has no separate upgrade engine (no upgrade plan, pre-upgrade
+snapshot or three-way merge): installing the new version over the old one is a
+re-install with `overwrite: true`, and the package rollback it serves restores a
+package commit. See [Package Management](/docs/api/metadata-api#package-management)
+in the Metadata API.
diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts/kernel.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts/kernel.md
index b1fe0315f7a..3625c957d44 100644
--- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts/kernel.md
+++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts/kernel.md
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
| Dir | Sites |
|---|---|
-| `kernel/` | 247 |
+| `kernel/` | 237 |
diff --git a/packages/spec/api-surface/contracts.json b/packages/spec/api-surface/contracts.json
index b6107fcb1a9..7e5114fba47 100644
--- a/packages/spec/api-surface/contracts.json
+++ b/packages/spec/api-surface/contracts.json
@@ -97,7 +97,6 @@
"EngineSchemaRegistryView (interface)",
"EngineTransactionInfo (interface)",
"EngineTransactionOptions (interface)",
- "ExecuteUpgradeInput (interface)",
"ExplainAccessRequest (interface)",
"FILE_ATTACHMENT_SERVICE (const)",
"FLOW_UNBOUND_CAUSES (const)",
@@ -229,7 +228,6 @@
"PRINCIPAL_SESSION_MAX_TTL_SECONDS (const)",
"PendingActionRow (interface)",
"PendingActionStatus (type)",
- "PlanUpgradeInput (interface)",
"Plugin (interface)",
"PluginStartupResult (type)",
"PositionGrantSummary (interface)",
@@ -266,7 +264,6 @@
"ResumeFailureReport (interface)",
"ResumeSignal (interface)",
"RlsMembershipContext (interface)",
- "RollbackInput (interface)",
"RouteHandler (type)",
"RunListResult (interface)",
"SEED_SETTLEMENT_SERVICE (const)",
diff --git a/packages/spec/api-surface/kernel.json b/packages/spec/api-surface/kernel.json
index dde89b4d2b2..fd84a0f13fd 100644
--- a/packages/spec/api-surface/kernel.json
+++ b/packages/spec/api-surface/kernel.json
@@ -169,13 +169,8 @@
"MetadataBulkResultSchema (const)",
"MetadataCategory (type)",
"MetadataCategoryEnum (const)",
- "MetadataChangeType (type)",
- "MetadataChangeTypeSchema (const)",
"MetadataDependency (type)",
"MetadataDependencySchema (const)",
- "MetadataDiffItem (type)",
- "MetadataDiffItemParsed (type)",
- "MetadataDiffItemSchema (const)",
"MetadataFallbackStrategy (type)",
"MetadataFallbackStrategySchema (const)",
"MetadataLock (type)",
@@ -366,11 +361,6 @@
"ResolvedPackageDependencySchema (const)",
"ResourceType (type)",
"ResourceTypeSchema (const)",
- "RollbackPackageRequest (type)",
- "RollbackPackageRequestParsed (type)",
- "RollbackPackageRequestSchema (const)",
- "RollbackPackageResponse (type)",
- "RollbackPackageResponseSchema (const)",
"RuntimeConfig (type)",
"RuntimeConfigParsed (type)",
"RuntimeConfigSchema (const)",
@@ -435,22 +425,6 @@
"UninstallPackageResponseSchema (const)",
"UnknownAuthoringKeyFinding (interface)",
"UnmanagedInstallKeyDisposition (type)",
- "UpgradeImpactLevel (type)",
- "UpgradeImpactLevelSchema (const)",
- "UpgradePackageRequest (type)",
- "UpgradePackageRequestParsed (type)",
- "UpgradePackageRequestSchema (const)",
- "UpgradePackageResponse (type)",
- "UpgradePackageResponseParsed (type)",
- "UpgradePackageResponseSchema (const)",
- "UpgradePhase (type)",
- "UpgradePhaseSchema (const)",
- "UpgradePlan (type)",
- "UpgradePlanParsed (type)",
- "UpgradePlanSchema (const)",
- "UpgradeSnapshot (type)",
- "UpgradeSnapshotParsed (type)",
- "UpgradeSnapshotSchema (const)",
"VIEW_LAYOUT_WITHOUT_BINDING (const)",
"VIEW_ROW_COLOR_UNRESOLVABLE_VALUE (const)",
"VIEW_ROW_COLOR_WITHOUT_COLORS (const)",
diff --git a/packages/spec/authorable-defaults/kernel.json b/packages/spec/authorable-defaults/kernel.json
index be3bbedbb4b..f486a2b2d20 100644
--- a/packages/spec/authorable-defaults/kernel.json
+++ b/packages/spec/authorable-defaults/kernel.json
@@ -52,7 +52,6 @@
"kernel/KernelContext:mode = \"production\"",
"kernel/Manifest:defaultDatasource = \"default\"",
"kernel/Manifest:scope = \"project\"",
- "kernel/MetadataDiffItem:hasConflict = false",
"kernel/MetadataManagerConfig:fallback = \"none\"",
"kernel/MetadataManagerConfig:formats = [\"typescript\",\"json\",\"yaml\"]",
"kernel/MetadataManagerConfig:tableName = \"sys_metadata\"",
@@ -116,7 +115,6 @@
"kernel/RealTimeNotificationConfig:tenantFilter = true",
"kernel/RealTimeNotificationConfig:userFilter = true",
"kernel/ResolvedPackageDependency:type = \"required\"",
- "kernel/RollbackPackageRequest:rollbackCustomizations = true",
"kernel/RuntimeConfig:engine = \"v8-isolate\"",
"kernel/SBOM:format = \"cyclonedx\"",
"kernel/SBOMEntry:externalRefs = []",
@@ -136,12 +134,6 @@
"kernel/ServiceRegistryConfig:enableLogging = false",
"kernel/ServiceRegistryConfig:strictMode = true",
"kernel/TenantRuntimeContext:features = {}",
- "kernel/TenantRuntimeContext:mode = \"production\"",
- "kernel/UpgradePackageRequest:createSnapshot = true",
- "kernel/UpgradePackageRequest:dryRun = false",
- "kernel/UpgradePackageRequest:mergeStrategy = \"three-way-merge\"",
- "kernel/UpgradePackageRequest:skipValidation = false",
- "kernel/UpgradePlan:affectedCustomizations = 0",
- "kernel/UpgradePlan:requiresMigration = false"
+ "kernel/TenantRuntimeContext:mode = \"production\""
]
}
diff --git a/packages/spec/authorable-surface/kernel.json b/packages/spec/authorable-surface/kernel.json
index 7b150841a34..a7df5d9aa3d 100644
--- a/packages/spec/authorable-surface/kernel.json
+++ b/packages/spec/authorable-surface/kernel.json
@@ -297,12 +297,6 @@
"kernel/MetadataDependency:sourceType",
"kernel/MetadataDependency:targetName",
"kernel/MetadataDependency:targetType",
- "kernel/MetadataDiffItem:changeType",
- "kernel/MetadataDiffItem:hasConflict",
- "kernel/MetadataDiffItem:name",
- "kernel/MetadataDiffItem:previousName",
- "kernel/MetadataDiffItem:summary",
- "kernel/MetadataDiffItem:type",
"kernel/MetadataManagerConfig:cache",
"kernel/MetadataManagerConfig:datasource",
"kernel/MetadataManagerConfig:fallback",
@@ -638,12 +632,6 @@
"kernel/ResolvedPackageDependency:resolvedVersion",
"kernel/ResolvedPackageDependency:type",
"kernel/ResolvedPackageDependency:versionConstraint",
- "kernel/RollbackPackageRequest:packageId",
- "kernel/RollbackPackageRequest:rollbackCustomizations",
- "kernel/RollbackPackageRequest:snapshotId",
- "kernel/RollbackPackageResponse:message",
- "kernel/RollbackPackageResponse:restoredVersion",
- "kernel/RollbackPackageResponse:success",
"kernel/RuntimeConfig:engine",
"kernel/RuntimeConfig:engineConfig",
"kernel/RuntimeConfig:resourceLimits",
@@ -751,42 +739,6 @@
"kernel/UninstallPackageResponse:id",
"kernel/UninstallPackageResponse:message",
"kernel/UninstallPackageResponse:success",
- "kernel/UpgradePackageRequest:createSnapshot",
- "kernel/UpgradePackageRequest:dryRun",
- "kernel/UpgradePackageRequest:manifest",
- "kernel/UpgradePackageRequest:mergeStrategy",
- "kernel/UpgradePackageRequest:packageId",
- "kernel/UpgradePackageRequest:skipValidation",
- "kernel/UpgradePackageRequest:targetVersion",
- "kernel/UpgradePackageResponse:conflicts",
- "kernel/UpgradePackageResponse:errorMessage",
- "kernel/UpgradePackageResponse:message",
- "kernel/UpgradePackageResponse:phase",
- "kernel/UpgradePackageResponse:plan",
- "kernel/UpgradePackageResponse:snapshotId",
- "kernel/UpgradePackageResponse:success",
- "kernel/UpgradePlan:affectedCustomizations",
- "kernel/UpgradePlan:changes",
- "kernel/UpgradePlan:dependencyUpgrades",
- "kernel/UpgradePlan:estimatedDuration [RETIRED]",
- "kernel/UpgradePlan:estimatedDurationSeconds",
- "kernel/UpgradePlan:fromVersion",
- "kernel/UpgradePlan:impactLevel",
- "kernel/UpgradePlan:migrationScripts",
- "kernel/UpgradePlan:packageId",
- "kernel/UpgradePlan:requiresMigration",
- "kernel/UpgradePlan:summary",
- "kernel/UpgradePlan:toVersion",
- "kernel/UpgradeSnapshot:createdAt",
- "kernel/UpgradeSnapshot:customizationSnapshot",
- "kernel/UpgradeSnapshot:expiresAt",
- "kernel/UpgradeSnapshot:fromVersion",
- "kernel/UpgradeSnapshot:id",
- "kernel/UpgradeSnapshot:metadataSnapshot",
- "kernel/UpgradeSnapshot:packageId",
- "kernel/UpgradeSnapshot:previousManifest",
- "kernel/UpgradeSnapshot:tenantId",
- "kernel/UpgradeSnapshot:toVersion",
"kernel/ValidationError:code",
"kernel/ValidationError:field",
"kernel/ValidationError:message",
diff --git a/packages/spec/declaration-map/kernel.json b/packages/spec/declaration-map/kernel.json
index 960d01a361f..c5e9b481a30 100644
--- a/packages/spec/declaration-map/kernel.json
+++ b/packages/spec/declaration-map/kernel.json
@@ -113,12 +113,8 @@
"MetadataBulkResult": "kernel/MetadataBulkResult",
"MetadataBulkResultSchema": "kernel/MetadataBulkResult",
"MetadataCategoryEnum": "kernel/MetadataCategoryEnum",
- "MetadataChangeType": "kernel/MetadataChangeType",
- "MetadataChangeTypeSchema": "kernel/MetadataChangeType",
"MetadataDependency": "kernel/MetadataDependency",
"MetadataDependencySchema": "kernel/MetadataDependency",
- "MetadataDiffItem": "kernel/MetadataDiffItem",
- "MetadataDiffItemSchema": "kernel/MetadataDiffItem",
"MetadataFallbackStrategy": "kernel/MetadataFallbackStrategy",
"MetadataFallbackStrategySchema": "kernel/MetadataFallbackStrategy",
"MetadataLock": "kernel/MetadataLock",
@@ -250,10 +246,6 @@
"ResolvedPackageDependencySchema": "kernel/ResolvedPackageDependency",
"ResourceType": "kernel/ResourceType",
"ResourceTypeSchema": "kernel/ResourceType",
- "RollbackPackageRequest": "kernel/RollbackPackageRequest",
- "RollbackPackageRequestSchema": "kernel/RollbackPackageRequest",
- "RollbackPackageResponse": "kernel/RollbackPackageResponse",
- "RollbackPackageResponseSchema": "kernel/RollbackPackageResponse",
"RuntimeConfig": "kernel/RuntimeConfig",
"RuntimeConfigSchema": "kernel/RuntimeConfig",
"RuntimeMode": "kernel/RuntimeMode",
@@ -294,18 +286,6 @@
"UninstallPackageRequestSchema": "kernel/UninstallPackageRequest",
"UninstallPackageResponse": "kernel/UninstallPackageResponse",
"UninstallPackageResponseSchema": "kernel/UninstallPackageResponse",
- "UpgradeImpactLevel": "kernel/UpgradeImpactLevel",
- "UpgradeImpactLevelSchema": "kernel/UpgradeImpactLevel",
- "UpgradePackageRequest": "kernel/UpgradePackageRequest",
- "UpgradePackageRequestSchema": "kernel/UpgradePackageRequest",
- "UpgradePackageResponse": "kernel/UpgradePackageResponse",
- "UpgradePackageResponseSchema": "kernel/UpgradePackageResponse",
- "UpgradePhase": "kernel/UpgradePhase",
- "UpgradePhaseSchema": "kernel/UpgradePhase",
- "UpgradePlan": "kernel/UpgradePlan",
- "UpgradePlanSchema": "kernel/UpgradePlan",
- "UpgradeSnapshot": "kernel/UpgradeSnapshot",
- "UpgradeSnapshotSchema": "kernel/UpgradeSnapshot",
"ValidationError": "kernel/ValidationError",
"ValidationErrorSchema": "kernel/ValidationError",
"ValidationResult": "kernel/ValidationResult",
diff --git a/packages/spec/dropped-refinements.baseline.json b/packages/spec/dropped-refinements.baseline.json
index d9bca71dedb..fa1f660ade2 100644
--- a/packages/spec/dropped-refinements.baseline.json
+++ b/packages/spec/dropped-refinements.baseline.json
@@ -2,8 +2,8 @@
"description": "Shrink-only ledger of every PUBLISHED JSON Schema that is STILL WIDER than the Zod type it was generated from, because a rule written as `.refine()` reaches the runtime and not the file (#18670). `z.toJSONSchema()` has no arm for a `custom` check: a plain record, the same record with a `.refine()`, and the same record with an ABORTING `.refine()` all project byte-identically (measured on zod 4.4.3, the version packages/spec resolves). So a document one of these files ACCEPTS can still be refused at parse time, and an author -- or an AI -- validating against packages/spec/json-schema/** finds out a release later. Each `sites` path is a position under that schema at which a refinement is dropped; the same paths are written onto the artifact itself as `x-dropped-refinements`. Item 2 closed the first patterns: a refinement DECLARED through the closed list in src/shared/refinement-projection.ts is emitted into the published file, reads `projected` rather than `dropped`, and its row LEAVES this ledger in the same PR -- which is why the ledger shrinks and never grows on a repair. Every refinement outside that closed list stays here, and adding an arm to the list is a public-contract decision, not a refactor. Hand-edited on purpose and with no `gen:` script: a generator would let a new gap be admitted by running a command instead of by a decision, which is the silence this ledger exists to end. Adding, removing or moving a site fails packages/spec/scripts/build-schemas.ts until the line moves with it, and the failure prints the corrected entry in full. ⛔ Do not delete or weaken a refinement to shorten this file -- the runtime rule is correct; it is the projection that is silent, and the remedy is to teach the closed list a NAMED pattern, never to drop the rule.",
"measured": {
"zod": "4.4.3",
- "publishedSchemasWithDroppedRefinements": 245,
- "droppedRefinementSites": 835,
+ "publishedSchemasWithDroppedRefinements": 243,
+ "droppedRefinementSites": 831,
"refinementSitesThatDidProject": 369,
"refinementSitesWithNoJsonFormToCompare": 0
},
@@ -1135,18 +1135,6 @@
""
]
},
- "kernel/UpgradePackageRequest": {
- "sites": [
- "manifest.navigationContributions.element.items.element.lazy",
- "manifest.navigationContributions.element.items.element.lazy.options[0]"
- ]
- },
- "kernel/UpgradeSnapshot": {
- "sites": [
- "previousManifest.navigationContributions.element.items.element.lazy",
- "previousManifest.navigationContributions.element.items.element.lazy.options[0]"
- ]
- },
"security/CriteriaSharingRule": {
"sites": [
"sharedWith"
diff --git a/packages/spec/export-origins/contracts.json b/packages/spec/export-origins/contracts.json
index 1929d13975a..bd493c6eda4 100644
--- a/packages/spec/export-origins/contracts.json
+++ b/packages/spec/export-origins/contracts.json
@@ -97,7 +97,6 @@
"EngineSchemaRegistryView": "src/contracts/objectql-engine.ts#EngineSchemaRegistryView (interface)",
"EngineTransactionInfo": "src/contracts/objectql-engine.ts#EngineTransactionInfo (interface)",
"EngineTransactionOptions": "src/contracts/objectql-engine.ts#EngineTransactionOptions (interface)",
- "ExecuteUpgradeInput": "src/contracts/package-service.ts#ExecuteUpgradeInput (interface)",
"ExplainAccessRequest": "src/contracts/security-service.ts#ExplainAccessRequest (interface)",
"FILE_ATTACHMENT_SERVICE": "src/contracts/storage-service.ts#FILE_ATTACHMENT_SERVICE (const)",
"FLOW_UNBOUND_CAUSES": "src/contracts/automation-service.ts#FLOW_UNBOUND_CAUSES (const)",
@@ -229,7 +228,6 @@
"PRINCIPAL_SESSION_MAX_TTL_SECONDS": "src/contracts/auth-service.ts#PRINCIPAL_SESSION_MAX_TTL_SECONDS (const)",
"PendingActionRow": "src/contracts/ai-service.ts#PendingActionRow (interface)",
"PendingActionStatus": "src/contracts/ai-service.ts#PendingActionStatus (type)",
- "PlanUpgradeInput": "src/contracts/package-service.ts#PlanUpgradeInput (interface)",
"Plugin": "src/contracts/plugin-validator.ts#Plugin (interface)",
"PluginStartupResult": "src/kernel/startup-orchestrator.zod.ts#PluginStartupResult (type)",
"PositionGrantSummary": "src/contracts/security-service.ts#PositionGrantSummary (interface)",
@@ -266,7 +264,6 @@
"ResumeFailureReport": "src/contracts/approval-service.ts#ResumeFailureReport (interface)",
"ResumeSignal": "src/contracts/automation-service.ts#ResumeSignal (interface)",
"RlsMembershipContext": "src/contracts/rls-membership-resolver.ts#RlsMembershipContext (interface)",
- "RollbackInput": "src/contracts/package-service.ts#RollbackInput (interface)",
"RouteHandler": "src/contracts/http-server.ts#RouteHandler (type)",
"RunListResult": "src/contracts/automation-service.ts#RunListResult (interface)",
"SEED_SETTLEMENT_SERVICE": "src/contracts/seed-settlement.ts#SEED_SETTLEMENT_SERVICE (const)",
diff --git a/packages/spec/export-origins/kernel.json b/packages/spec/export-origins/kernel.json
index db2f33dd37e..ce8494045ea 100644
--- a/packages/spec/export-origins/kernel.json
+++ b/packages/spec/export-origins/kernel.json
@@ -168,13 +168,8 @@
"MetadataBulkResultSchema": "src/kernel/metadata-plugin.zod.ts#MetadataBulkResultSchema (const)",
"MetadataCategory": "src/kernel/package-artifact.zod.ts#MetadataCategory (type)",
"MetadataCategoryEnum": "src/kernel/package-artifact.zod.ts#MetadataCategoryEnum (const)",
- "MetadataChangeType": "src/kernel/package-upgrade.zod.ts#MetadataChangeType (type)",
- "MetadataChangeTypeSchema": "src/kernel/package-upgrade.zod.ts#MetadataChangeTypeSchema (const)",
"MetadataDependency": "src/kernel/metadata-plugin.zod.ts#MetadataDependency (type)",
"MetadataDependencySchema": "src/kernel/metadata-plugin.zod.ts#MetadataDependencySchema (const)",
- "MetadataDiffItem": "src/kernel/package-upgrade.zod.ts#MetadataDiffItem (type)",
- "MetadataDiffItemParsed": "src/kernel/package-upgrade.zod.ts#MetadataDiffItemParsed (type)",
- "MetadataDiffItemSchema": "src/kernel/package-upgrade.zod.ts#MetadataDiffItemSchema (const)",
"MetadataFallbackStrategy": "src/kernel/metadata-loader.zod.ts#MetadataFallbackStrategy (type)",
"MetadataFallbackStrategySchema": "src/kernel/metadata-loader.zod.ts#MetadataFallbackStrategySchema (const)",
"MetadataLock": "src/kernel/metadata-protection.zod.ts#MetadataLock (type)",
@@ -365,11 +360,6 @@
"ResolvedPackageDependencySchema": "src/kernel/plugin-security.zod.ts#ResolvedPackageDependencySchema (const)",
"ResourceType": "src/kernel/plugin-security-advanced.zod.ts#ResourceType (type)",
"ResourceTypeSchema": "src/kernel/plugin-security-advanced.zod.ts#ResourceTypeSchema (const)",
- "RollbackPackageRequest": "src/kernel/package-upgrade.zod.ts#RollbackPackageRequest (type)",
- "RollbackPackageRequestParsed": "src/kernel/package-upgrade.zod.ts#RollbackPackageRequestParsed (type)",
- "RollbackPackageRequestSchema": "src/kernel/package-upgrade.zod.ts#RollbackPackageRequestSchema (const)",
- "RollbackPackageResponse": "src/kernel/package-upgrade.zod.ts#RollbackPackageResponse (type)",
- "RollbackPackageResponseSchema": "src/kernel/package-upgrade.zod.ts#RollbackPackageResponseSchema (const)",
"RuntimeConfig": "src/kernel/plugin-security-advanced.zod.ts#RuntimeConfig (type)",
"RuntimeConfigParsed": "src/kernel/plugin-security-advanced.zod.ts#RuntimeConfigParsed (type)",
"RuntimeConfigSchema": "src/kernel/plugin-security-advanced.zod.ts#RuntimeConfigSchema (const)",
@@ -432,22 +422,6 @@
"UninstallPackageResponseSchema": "src/kernel/package-registry.zod.ts#UninstallPackageResponseSchema (const)",
"UnknownAuthoringKeyFinding": "src/data/authoring-key-lint.ts#UnknownAuthoringKeyFinding (interface)",
"UnmanagedInstallKeyDisposition": "src/kernel/unmanaged-install.ts#UnmanagedInstallKeyDisposition (type)",
- "UpgradeImpactLevel": "src/kernel/package-upgrade.zod.ts#UpgradeImpactLevel (type)",
- "UpgradeImpactLevelSchema": "src/kernel/package-upgrade.zod.ts#UpgradeImpactLevelSchema (const)",
- "UpgradePackageRequest": "src/kernel/package-upgrade.zod.ts#UpgradePackageRequest (type)",
- "UpgradePackageRequestParsed": "src/kernel/package-upgrade.zod.ts#UpgradePackageRequestParsed (type)",
- "UpgradePackageRequestSchema": "src/kernel/package-upgrade.zod.ts#UpgradePackageRequestSchema (const)",
- "UpgradePackageResponse": "src/kernel/package-upgrade.zod.ts#UpgradePackageResponse (type)",
- "UpgradePackageResponseParsed": "src/kernel/package-upgrade.zod.ts#UpgradePackageResponseParsed (type)",
- "UpgradePackageResponseSchema": "src/kernel/package-upgrade.zod.ts#UpgradePackageResponseSchema (const)",
- "UpgradePhase": "src/kernel/package-upgrade.zod.ts#UpgradePhase (type)",
- "UpgradePhaseSchema": "src/kernel/package-upgrade.zod.ts#UpgradePhaseSchema (const)",
- "UpgradePlan": "src/kernel/package-upgrade.zod.ts#UpgradePlan (type)",
- "UpgradePlanParsed": "src/kernel/package-upgrade.zod.ts#UpgradePlanParsed (type)",
- "UpgradePlanSchema": "src/kernel/package-upgrade.zod.ts#UpgradePlanSchema (const)",
- "UpgradeSnapshot": "src/kernel/package-upgrade.zod.ts#UpgradeSnapshot (type)",
- "UpgradeSnapshotParsed": "src/kernel/package-upgrade.zod.ts#UpgradeSnapshotParsed (type)",
- "UpgradeSnapshotSchema": "src/kernel/package-upgrade.zod.ts#UpgradeSnapshotSchema (const)",
"VIEW_LAYOUT_WITHOUT_BINDING": "src/kernel/functional-completeness.ts#VIEW_LAYOUT_WITHOUT_BINDING (const)",
"VIEW_ROW_COLOR_UNRESOLVABLE_VALUE": "src/kernel/functional-completeness.ts#VIEW_ROW_COLOR_UNRESOLVABLE_VALUE (const)",
"VIEW_ROW_COLOR_WITHOUT_COLORS": "src/kernel/functional-completeness.ts#VIEW_ROW_COLOR_WITHOUT_COLORS (const)",
diff --git a/packages/spec/json-schema.manifest/kernel.json b/packages/spec/json-schema.manifest/kernel.json
index 2ab9ed9e83a..2242bf2347e 100644
--- a/packages/spec/json-schema.manifest/kernel.json
+++ b/packages/spec/json-schema.manifest/kernel.json
@@ -60,9 +60,7 @@
"kernel/ManifestPermissions",
"kernel/MetadataBulkResult",
"kernel/MetadataCategoryEnum",
- "kernel/MetadataChangeType",
"kernel/MetadataDependency",
- "kernel/MetadataDiffItem",
"kernel/MetadataFallbackStrategy",
"kernel/MetadataLock",
"kernel/MetadataLockSource",
@@ -129,8 +127,6 @@
"kernel/ResolvedDependency",
"kernel/ResolvedPackageDependency",
"kernel/ResourceType",
- "kernel/RollbackPackageRequest",
- "kernel/RollbackPackageResponse",
"kernel/RuntimeConfig",
"kernel/RuntimeMode",
"kernel/SBOM",
@@ -152,12 +148,6 @@
"kernel/TenantRuntimeContext",
"kernel/UninstallPackageRequest",
"kernel/UninstallPackageResponse",
- "kernel/UpgradeImpactLevel",
- "kernel/UpgradePackageRequest",
- "kernel/UpgradePackageResponse",
- "kernel/UpgradePhase",
- "kernel/UpgradePlan",
- "kernel/UpgradeSnapshot",
"kernel/ValidationError",
"kernel/ValidationResult",
"kernel/ValidationWarning",
diff --git a/packages/spec/llms.txt b/packages/spec/llms.txt
index 02279212134..31572f14da7 100644
--- a/packages/spec/llms.txt
+++ b/packages/spec/llms.txt
@@ -77,7 +77,7 @@ const query = {
---
-## 3. Schema Inventory by Domain (206 schemas)
+## 3. Schema Inventory by Domain (205 schemas)
Counted as `*.zod.ts` modules under `packages/spec/src//` — the sources
that ship in this tarball (`files` includes `src/**/*.zod.ts`), so every number
@@ -86,7 +86,7 @@ here is verifiable from the installed package.
| Domain | Count | Key Schemas |
|--------|-------|-------------|
| system | 36 | Auth, Cache, Dev Login, Email / SMS Config, Encryption, Fate Supplement (organization ownership), HTTP Server, License, Logging, Metrics |
-| kernel | 31 | Plugin, Manifest, Events (6 sub-modules), Feature, Context, Package Registry |
+| kernel | 30 | Plugin, Manifest, Events (6 sub-modules), Feature, Context, Package Registry |
| data | 30 | Object, Field, Picklist, Query, Filter, Driver (SQL/NoSQL/Memory/Mongo/Postgres), Cube |
| api | 31 | Endpoint, REST Server, Discovery, OData, Batch, WebSocket, Response Envelope, Package Lifecycle, Package API (assembled stage) |
| ui | 18 | View, App, Action, Dashboard, Page, Chart, Component, Animation |
diff --git a/packages/spec/scripts/build-docs.ts b/packages/spec/scripts/build-docs.ts
index 3152dfab62a..53cd2957c39 100644
--- a/packages/spec/scripts/build-docs.ts
+++ b/packages/spec/scripts/build-docs.ts
@@ -655,7 +655,7 @@ const SECTION_GROUPS: Record
kernel: [
{ section: 'Plugin Lifecycle', pages: ['plugin', 'plugin-lifecycle-events', 'plugin-lifecycle-advanced', 'plugin-runtime', 'plugin-loading', 'plugin-registry', 'plugin-structure', 'plugin-validator'] },
{ section: 'Plugin Security & Dependencies', pages: ['plugin-security', 'plugin-security-advanced', 'plugin-capability', 'plugin-versioning', 'dependency-resolution', 'manifest'] },
- { section: 'Packages', pages: ['package-artifact', 'package-registry', 'package-upgrade'] },
+ { section: 'Packages', pages: ['package-artifact', 'package-registry'] },
{ section: 'Metadata & Runtime', pages: ['metadata-plugin', 'metadata-loader', 'metadata-protection', 'metadata-persistence', 'misc', 'context', 'execution-context', 'service-registry', 'startup-orchestrator', 'cluster', 'feature', 'cli-extension', 'dev-plugin', 'state-machine'] },
],
// [#16325] The package & marketplace FORMAT half of what used to be `cloud/`.
diff --git a/packages/spec/scripts/file-description.test.ts b/packages/spec/scripts/file-description.test.ts
index 1ff07334fd6..0dd904eb228 100644
--- a/packages/spec/scripts/file-description.test.ts
+++ b/packages/spec/scripts/file-description.test.ts
@@ -2223,10 +2223,12 @@ describe('corpus — every rendered description is well-formed markdown', () =>
// `marketplace-admin` and the `environment-artifact` re-export; `tenant`
// opened at level 2 and was never counted. `marketplace.zod.ts`, the one
// level-1 opener among the four modules that moved to `marketplace/`,
- // still counts.)
- expect(shifted.length).toBe(31);
+ // still counts. 31 -> 30: `kernel/package-upgrade.zod.ts` — a level-1
+ // opener (`# Package Upgrade Protocol`) — was removed whole by #25608's
+ // ADR-0049 retirement.)
+ expect(shifted.length).toBe(30);
// …and every one of them was shifted because it opened at level 1.
- expect(shifted.filter(d => /^ {0,3}#(?:[ \t]|$)/m.test(withoutFences(d.untouched)))).toHaveLength(31);
+ expect(shifted.filter(d => /^ {0,3}#(?:[ \t]|$)/m.test(withoutFences(d.untouched)))).toHaveLength(30);
});
it('never opens a page on the `@module` marker (#13796)', () => {
diff --git a/packages/spec/src/api/package-api.test.ts b/packages/spec/src/api/package-api.test.ts
index a7807cc9bc9..c15a87ca424 100644
--- a/packages/spec/src/api/package-api.test.ts
+++ b/packages/spec/src/api/package-api.test.ts
@@ -290,22 +290,22 @@ describe('PackageUpgradeResponseSchema', () => {
success: true,
phase: 'completed',
snapshotId: 'snap_001',
- plan: {
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'medium',
- changes: [
- { type: 'object', name: 'account', changeType: 'modified', summary: 'Added fields' },
- ],
- },
message: 'Upgrade completed successfully',
},
});
expect(result.data.success).toBe(true);
expect(result.data.phase).toBe('completed');
expect(result.data.snapshotId).toBe('snap_001');
- expect(result.data.plan?.changes).toHaveLength(1);
+ });
+
+ it('no longer declares `plan` — retired with the upgrade-plan model (#25608), so a stray one is stripped', () => {
+ const result = PackageUpgradeResponseSchema.parse({
+ success: true,
+ data: { success: true, phase: 'completed', plan: { packageId: 'com.acme.crm' } },
+ });
+ expect(result.data).not.toHaveProperty('plan');
+ // Lit control: a declared sibling of the retired key still survives the parse.
+ expect(result.data.phase).toBe('completed');
});
it('should accept upgrade response with merge conflicts', () => {
diff --git a/packages/spec/src/api/package-api.zod.ts b/packages/spec/src/api/package-api.zod.ts
index 0b783e592de..f7410b2a370 100644
--- a/packages/spec/src/api/package-api.zod.ts
+++ b/packages/spec/src/api/package-api.zod.ts
@@ -4,7 +4,6 @@ import { z } from 'zod';
import { BaseResponseSchema } from './contract.zod';
import { InstalledPackageSchema } from '../kernel/package-registry.zod';
import { DependencyResolutionResultSchema } from '../kernel/dependency-resolution.zod';
-import { UpgradePlanSchema } from '../kernel/package-upgrade.zod';
import { PackageArtifactSchema } from '../kernel/package-artifact.zod';
import { ManifestSchema } from '../kernel/manifest.zod';
import { ArtifactReferenceSchema } from '../marketplace/marketplace.zod';
@@ -595,7 +594,9 @@ export const PackageUpgradeResponseSchema = lazySchema(() => BaseResponseSchema.
data: z.object({
success: z.boolean().describe('Whether the upgrade succeeded'),
phase: z.string().describe('Current upgrade phase'),
- plan: UpgradePlanSchema.optional().describe('Upgrade plan that was executed'),
+ // `plan` RETIRED (#25608, ADR-0049) with `kernel/package-upgrade.zod.ts`:
+ // it carried the zero-consumer `UpgradePlanSchema`, and no route answers
+ // this body (it is bound to none, see the section note above).
snapshotId: z.string().optional().describe('Snapshot ID for rollback'),
conflicts: z.array(z.object({
path: z.string().describe('Conflict path'),
diff --git a/packages/spec/src/contracts/package-service.test.ts b/packages/spec/src/contracts/package-service.test.ts
index 14dea660e64..d50e9ff5fad 100644
--- a/packages/spec/src/contracts/package-service.test.ts
+++ b/packages/spec/src/contracts/package-service.test.ts
@@ -7,7 +7,6 @@ import type {
} from './package-service';
import type { InstalledPackage } from '../kernel/package-registry.zod';
import type { DependencyResolutionResult } from '../kernel/dependency-resolution.zod';
-import type { UpgradePlan, UpgradePackageResponse, RollbackPackageResponse } from '../kernel/package-upgrade.zod';
describe('Package Service Contract', () => {
it('should allow a minimal IPackageService implementation with required methods', () => {
@@ -23,24 +22,12 @@ describe('Package Service Contract', () => {
installOrder: [],
}),
checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async () => ({
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'low',
- changes: [],
- }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
};
expect(typeof service.install).toBe('function');
expect(typeof service.uninstall).toBe('function');
expect(typeof service.resolveDependencies).toBe('function');
expect(typeof service.checkNamespaces).toBe('function');
- expect(typeof service.planUpgrade).toBe('function');
- expect(typeof service.upgrade).toBe('function');
- expect(typeof service.rollback).toBe('function');
});
it('should allow full implementation with optional methods', () => {
@@ -56,21 +43,10 @@ describe('Package Service Contract', () => {
installOrder: [],
}),
checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async () => ({
- packageId: 'test',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'none',
- changes: [],
- }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
- getSnapshot: async () => null,
uploadArtifact: async () => ({ success: true }),
togglePackage: async () => ({} as InstalledPackage),
};
- expect(service.getSnapshot).toBeDefined();
expect(service.uploadArtifact).toBeDefined();
expect(service.togglePackage).toBeDefined();
});
@@ -99,9 +75,6 @@ describe('Package Service Contract', () => {
listPackages: async () => [],
resolveDependencies: async () => ({ dependencies: [], canProceed: true, requiredActions: [], installOrder: [] }),
checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async () => ({ packageId: '', fromVersion: '', toVersion: '', impactLevel: 'none', changes: [] }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
};
const result = await service.install({
@@ -133,9 +106,6 @@ describe('Package Service Contract', () => {
suggestion: 'acme_crm',
}],
}),
- planUpgrade: async () => ({ packageId: '', fromVersion: '', toVersion: '', impactLevel: 'none', changes: [] }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
};
const result = await service.checkNamespaces({
@@ -149,88 +119,6 @@ describe('Package Service Contract', () => {
expect(result.conflicts[0].suggestion).toBe('acme_crm');
});
- it('should generate an upgrade plan with impact analysis', async () => {
- const service: IPackageService = {
- install: async () => ({ package: {} as InstalledPackage }),
- uninstall: async () => ({ success: true }),
- getPackage: async () => null,
- listPackages: async () => [],
- resolveDependencies: async () => ({ dependencies: [], canProceed: true, requiredActions: [], installOrder: [] }),
- checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async (): Promise => ({
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'high',
- changes: [
- { type: 'object', name: 'account', changeType: 'modified', summary: 'Added 3 new fields' },
- { type: 'view', name: 'account_list', changeType: 'modified', summary: 'Updated column layout' },
- { type: 'flow', name: 'onboarding_flow', changeType: 'added', summary: 'New onboarding automation' },
- ],
- requiresMigration: true,
- migrationScripts: ['migrations/v2_add_account_fields.ts'],
- dependencyUpgrades: [{ packageId: 'com.acme.core', fromVersion: '1.2.0', toVersion: '2.0.0' }],
- estimatedDurationSeconds: 120,
- summary: 'Major upgrade with 3 metadata changes and 1 migration',
- }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
- };
-
- const plan = await service.planUpgrade({
- packageId: 'com.acme.crm',
- targetVersion: '2.0.0',
- });
-
- expect(plan.impactLevel).toBe('high');
- expect(plan.changes).toHaveLength(3);
- expect(plan.requiresMigration).toBe(true);
- expect(plan.migrationScripts).toHaveLength(1);
- expect(plan.dependencyUpgrades).toHaveLength(1);
- expect(plan.estimatedDurationSeconds).toBe(120);
- });
-
- it('should execute upgrade and support rollback', async () => {
- const service: IPackageService = {
- install: async () => ({ package: {} as InstalledPackage }),
- uninstall: async () => ({ success: true }),
- getPackage: async () => null,
- listPackages: async () => [],
- resolveDependencies: async () => ({ dependencies: [], canProceed: true, requiredActions: [], installOrder: [] }),
- checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async () => ({ packageId: '', fromVersion: '', toVersion: '', impactLevel: 'none', changes: [] }),
- upgrade: async (): Promise => ({
- success: true,
- phase: 'completed',
- snapshotId: 'snap_001',
- message: 'Upgrade completed successfully',
- }),
- rollback: async (): Promise => ({
- success: true,
- restoredVersion: '1.0.0',
- message: 'Rolled back to version 1.0.0',
- }),
- };
-
- const upgradeResult = await service.upgrade({
- packageId: 'com.acme.crm',
- targetVersion: '2.0.0',
- createSnapshot: true,
- mergeStrategy: 'three-way-merge',
- });
- expect(upgradeResult.success).toBe(true);
- expect(upgradeResult.phase).toBe('completed');
- expect(upgradeResult.snapshotId).toBe('snap_001');
-
- const rollbackResult = await service.rollback({
- packageId: 'com.acme.crm',
- snapshotId: 'snap_001',
- rollbackCustomizations: true,
- });
- expect(rollbackResult.success).toBe(true);
- expect(rollbackResult.restoredVersion).toBe('1.0.0');
- });
-
it('should resolve dependencies with platform compatibility', async () => {
const service: IPackageService = {
install: async () => ({ package: {} as InstalledPackage }),
@@ -249,9 +137,6 @@ describe('Package Service Contract', () => {
installOrder: ['com.acme.core', 'com.acme.ui', 'com.acme.crm'],
}),
checkNamespaces: async () => ({ available: true, conflicts: [] }),
- planUpgrade: async () => ({ packageId: '', fromVersion: '', toVersion: '', impactLevel: 'none', changes: [] }),
- upgrade: async () => ({ success: true, phase: 'completed' }),
- rollback: async () => ({ success: true }),
};
const result = await service.resolveDependencies({
diff --git a/packages/spec/src/contracts/package-service.ts b/packages/spec/src/contracts/package-service.ts
index faa94ef5a76..52f2f9a786c 100644
--- a/packages/spec/src/contracts/package-service.ts
+++ b/packages/spec/src/contracts/package-service.ts
@@ -7,7 +7,6 @@
* - Package installation with dependency resolution
* - Platform compatibility enforcement
* - Namespace conflict detection
- * - Package upgrade lifecycle (plan → snapshot → execute → validate → rollback)
*
* Follows Dependency Inversion Principle - consumers depend on this interface,
* not on concrete package manager implementations.
@@ -15,18 +14,12 @@
* ## Architecture Alignment
* - **Salesforce**: PackageInstallHandler with dependency validation
* - **npm**: pacote + arborist for install/resolve
- * - **Helm**: helm install/upgrade/rollback lifecycle
+ * - **Helm**: helm install lifecycle
* - **Kubernetes**: controller-runtime reconcile loop
*/
import type { InstalledPackage, NamespaceConflictError } from '../kernel/package-registry.zod';
import type { DependencyResolutionResult } from '../kernel/dependency-resolution.zod';
-import type {
- UpgradePlan,
- UpgradeSnapshot,
- UpgradePackageResponse,
- RollbackPackageResponse,
-} from '../kernel/package-upgrade.zod';
import type { PackageArtifact } from '../kernel/package-artifact.zod';
import type { ObjectStackManifest } from '../kernel/manifest.zod';
@@ -100,51 +93,10 @@ export interface CheckNamespaceResult {
conflicts: NamespaceConflictError[];
}
-// ==========================================
-// Upgrade Types
-// ==========================================
-
-/**
- * Input for generating an upgrade plan.
- */
-export interface PlanUpgradeInput {
- /** Package ID to upgrade */
- packageId: string;
- /** Target version to upgrade to */
- targetVersion?: string;
- /** New manifest for the target version */
- manifest?: ObjectStackManifest;
-}
-
-/**
- * Input for executing an upgrade.
- */
-export interface ExecuteUpgradeInput {
- /** Package ID to upgrade */
- packageId: string;
- /** Target version */
- targetVersion?: string;
- /** New manifest */
- manifest?: ObjectStackManifest;
- /** Whether to create a pre-upgrade snapshot */
- createSnapshot?: boolean;
- /** Merge strategy for customizations */
- mergeStrategy?: 'keep-custom' | 'accept-incoming' | 'three-way-merge';
- /** Whether to run in dry-run mode */
- dryRun?: boolean;
-}
-
-/**
- * Input for rolling back a package.
- */
-export interface RollbackInput {
- /** Package ID to rollback */
- packageId: string;
- /** Snapshot ID to restore from */
- snapshotId: string;
- /** Whether to also rollback customizations */
- rollbackCustomizations?: boolean;
-}
+// The upgrade types (`PlanUpgradeInput`, `ExecuteUpgradeInput`,
+// `RollbackInput`) were RETIRED with `kernel/package-upgrade.zod.ts` (#25608,
+// ADR-0049 enforce-or-remove) together with the four `IPackageService`
+// members that took them: no provider ever implemented any of them.
// ==========================================
// Artifact Upload Types
@@ -274,37 +226,11 @@ export interface IPackageService {
*/
checkNamespaces(input: CheckNamespaceInput): Promise;
- // ---- Upgrade Lifecycle ----
-
- /**
- * Generate an upgrade plan (preview without executing).
- * Analyzes metadata diff, impact level, migration requirements,
- * and dependency cascades.
- * @param input - Upgrade planning parameters
- * @returns Upgrade plan with impact analysis
- */
- planUpgrade(input: PlanUpgradeInput): Promise;
-
- /**
- * Execute a package upgrade with optional snapshot and rollback support.
- * @param input - Upgrade execution parameters
- * @returns Upgrade response with phase, plan, and conflicts
- */
- upgrade(input: ExecuteUpgradeInput): Promise;
-
- /**
- * Rollback a package to a previous snapshot.
- * @param input - Rollback parameters
- * @returns Rollback result
- */
- rollback(input: RollbackInput): Promise;
-
- /**
- * Get an upgrade snapshot by ID.
- * @param snapshotId - Snapshot identifier
- * @returns Snapshot or null
- */
- getSnapshot?(snapshotId: string): Promise;
+ // ---- Upgrade Lifecycle ---- RETIRED (#25608): `planUpgrade`, `upgrade`,
+ // `rollback` and `getSnapshot` left with the zero-consumer upgrade model.
+ // The rollback the platform serves is the ADR-0067 commit rollback
+ // (`POST /api/v1/packages/:id/rollback`, `RollbackToPackageCommitResponseSchema`);
+ // an upgrade is a re-install with `overwrite: true`.
// ---- Artifact Management ----
diff --git a/packages/spec/src/kernel/index.ts b/packages/spec/src/kernel/index.ts
index d8f85d5aff7..baf1437875c 100644
--- a/packages/spec/src/kernel/index.ts
+++ b/packages/spec/src/kernel/index.ts
@@ -56,7 +56,14 @@ export * from './metadata-authoring-lint';
export type { UnknownAuthoringKeyFinding } from '../data/authoring-key-lint';
export * from './package-artifact.zod';
export * from './package-registry.zod';
-export * from './package-upgrade.zod';
+// package-upgrade.zod (UpgradePlanSchema / MetadataDiffItemSchema /
+// MetadataChangeTypeSchema / UpgradeImpactLevelSchema / UpgradeSnapshotSchema /
+// UpgradePackageRequestSchema / UpgradePhaseSchema / UpgradePackageResponseSchema /
+// RollbackPackageRequestSchema / RollbackPackageResponseSchema) RETIRED whole
+// (#25608, ADR-0049 enforce-or-remove; ruling 6259332195 item 3): the upgrade
+// plan / diff / conflict / snapshot / rollback model had zero runtime consumers.
+// The declaration is RETIRED_DEFS_BY_MAJOR[18] plus the D3 semantic entry
+// `kernel-package-upgrade-family-retired`.
// [ADR-0131 D6] What an `unmanaged` install can copy: the one key
// classification `defineStack` and the install door's import planner read.
export * from './unmanaged-install';
diff --git a/packages/spec/src/kernel/package-upgrade-family-retirement.test.ts b/packages/spec/src/kernel/package-upgrade-family-retirement.test.ts
new file mode 100644
index 00000000000..7f41bc5dbf7
--- /dev/null
+++ b/packages/spec/src/kernel/package-upgrade-family-retirement.test.ts
@@ -0,0 +1,349 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { describe, expect, expectTypeOf, it } from 'vitest';
+
+import {
+ EXPORT_ENTRY_POINTS,
+ exportNamesOf,
+ holdersOf,
+} from '../../scripts/lib/export-origins-testkit';
+import { listRepoWalkFiles } from '../../scripts/lib/repo-walk-testkit';
+import type { IPackageService } from '../contracts/package-service';
+import { PackageUpgradeResponseSchema } from '../api/package-api.zod';
+import { CONVERSIONS_BY_MAJOR } from '../conversions/registry';
+import { MIGRATIONS_BY_MAJOR, RETIRED_DEFS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from '../migrations/registry';
+
+// ─── [#25608] `kernel/package-upgrade.zod.ts` is RETIRED WHOLE ───────────────
+//
+// ADR-0049 enforce-or-remove. Maintainer ruling A (`6259332195` on #24443,
+// item 3, 「其他同意」): "Retired: packages/spec/src/kernel/package-upgrade.zod.ts
+// (plan, diff, conflict, snapshot, rollback; zero runtime consumers), with its
+// ADR-0087 conversion." Done directly in 18.x with no retirement stage.
+//
+// The measurement that decided it, re-taken at this change's base
+// (objectstack `47af3dde42`) with a lit control beside every zero:
+//
+// 1. objectstack — no code outside `packages/spec` names any of the retired
+// names; the only hits are the generated reference pages, one link in
+// `content/docs/upgrading.mdx` and one ADR sentence.
+// 2. objectui main `a3c0da9` and the pinned `.objectui-sha` `2958f17e9c` —
+// zero files (control `@objectstack/spec`: 2123 / 2131 files).
+// 3. hotcrm `a4b3560` — zero files (control `@objectstack/spec`: 293 files).
+// 4. Providers — nothing implements `IPackageService`, so no `planUpgrade`,
+// `upgrade`, `rollback` or `getSnapshot` body ever existed.
+//
+// ## Why route 3, and why there is nothing to tombstone
+//
+// The retired shapes are a service's return values and HTTP bodies. None is a
+// stack collection or a metadata type, so there is no carrier key for a
+// `retiredKey()` tombstone and no authored document for an ADR-0087 D2
+// conversion to rewrite. The ruling's "ADR-0087 conversion" is therefore the
+// ledger record: the D3 `SemanticMigration` `kernel-package-upgrade-family-retired`
+// plus the ten `RETIRED_DEFS_BY_MAJOR[18]` entries the manifest-deletion gate
+// reads (the export-job and change-management precedents).
+//
+// Form follows `api/export-job-family-retirement.test.ts`.
+
+/** Every name the retirement removed from the public surface. */
+const RETIRED_NAMES = [
+ // kernel/package-upgrade.zod.ts — ten defs
+ 'MetadataChangeTypeSchema', 'MetadataChangeType',
+ 'MetadataDiffItemSchema', 'MetadataDiffItem', 'MetadataDiffItemParsed',
+ 'UpgradeImpactLevelSchema', 'UpgradeImpactLevel',
+ 'UpgradePlanSchema', 'UpgradePlan', 'UpgradePlanParsed',
+ 'UpgradeSnapshotSchema', 'UpgradeSnapshot', 'UpgradeSnapshotParsed',
+ 'UpgradePackageRequestSchema', 'UpgradePackageRequest', 'UpgradePackageRequestParsed',
+ 'UpgradePhaseSchema', 'UpgradePhase',
+ 'UpgradePackageResponseSchema', 'UpgradePackageResponse', 'UpgradePackageResponseParsed',
+ 'RollbackPackageRequestSchema', 'RollbackPackageRequest', 'RollbackPackageRequestParsed',
+ 'RollbackPackageResponseSchema', 'RollbackPackageResponse',
+ // contracts/package-service.ts — the inputs of the four retired members
+ 'PlanUpgradeInput', 'ExecuteUpgradeInput', 'RollbackInput',
+] as const;
+
+/** The ten def keys, spelled as `json-schema.manifest/` and the registry spell them. */
+const RETIRED_DEFS = [
+ 'kernel/MetadataChangeType', 'kernel/MetadataDiffItem', 'kernel/RollbackPackageRequest',
+ 'kernel/RollbackPackageResponse', 'kernel/UpgradeImpactLevel', 'kernel/UpgradePackageRequest',
+ 'kernel/UpgradePackageResponse', 'kernel/UpgradePhase', 'kernel/UpgradePlan', 'kernel/UpgradeSnapshot',
+] as const;
+
+const SEMANTIC_ID = 'kernel-package-upgrade-family-retired';
+
+/** Same-entry neighbours that stay — the package model the retired file sat beside. */
+const MUST_SURVIVE_KERNEL = ['PackageArtifactSchema', 'InstalledPackageSchema', 'ManifestSchema'] as const;
+const MUST_SURVIVE_API = ['PackageUpgradeRequestSchema', 'PackageUpgradeResponseSchema', 'RollbackToPackageCommitResponseSchema'] as const;
+const MUST_SURVIVE_CONTRACTS = ['IPackageService', 'InstallPackageInput', 'UploadArtifactInput'] as const;
+
+const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
+const SRC_ROOT = path.join(SPEC_ROOT, 'src');
+
+describe('package upgrade model retirement — the public surface', () => {
+ it('every retired name has ZERO holders on any public entry; the survivors still stand', () => {
+ for (const needed of ['.', './kernel', './contracts', './api']) {
+ expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed);
+ }
+ expect(exportNamesOf('./kernel').length, './kernel must export a non-trivial surface').toBeGreaterThan(100);
+
+ for (const name of RETIRED_NAMES) {
+ expect(holdersOf(name), `${name} must have zero holders after the package upgrade retirement`).toEqual([]);
+ }
+ const survivors: Array<[string, readonly string[]]> = [
+ ['./kernel', MUST_SURVIVE_KERNEL],
+ ['./api', MUST_SURVIVE_API],
+ ['./contracts', MUST_SURVIVE_CONTRACTS],
+ ];
+ for (const [entry, names] of survivors) {
+ const exported = exportNamesOf(entry);
+ for (const name of names) expect(exported, `${name} must SURVIVE this retirement`).toContain(name);
+ }
+ });
+
+ it('runtime namespaces agree with the compiler view', async () => {
+ const kernel = await import('./index');
+ const root = await import('../index');
+ for (const name of RETIRED_NAMES.filter((n) => n.endsWith('Schema'))) {
+ expect(name in kernel, `kernel must not export ${name}`).toBe(false);
+ expect(name in root, `the root entry must not export ${name}`).toBe(false);
+ }
+ for (const name of MUST_SURVIVE_KERNEL) expect(name in kernel, `${name} must SURVIVE at runtime`).toBe(true);
+ });
+
+ it('IPackageService carries none of the four upgrade members; its install surface stands', () => {
+ expectTypeOf<'planUpgrade' extends keyof IPackageService ? true : false>().toEqualTypeOf();
+ expectTypeOf<'upgrade' extends keyof IPackageService ? true : false>().toEqualTypeOf();
+ expectTypeOf<'rollback' extends keyof IPackageService ? true : false>().toEqualTypeOf();
+ expectTypeOf<'getSnapshot' extends keyof IPackageService ? true : false>().toEqualTypeOf();
+ // Lit control: the same probe answers true for a member that stays.
+ expectTypeOf<'install' extends keyof IPackageService ? true : false>().toEqualTypeOf();
+ });
+
+ it('PackageUpgradeResponse.data no longer declares `plan`; its declared siblings stand', () => {
+ const data = PackageUpgradeResponseSchema.shape.data;
+ expect(Object.keys(data.shape)).not.toContain('plan');
+ expect(Object.keys(data.shape)).toEqual(expect.arrayContaining(['success', 'phase', 'snapshotId', 'conflicts']));
+ });
+
+ it('the module is gone from disk, and nothing in the package imports it any more', () => {
+ for (const f of ['package-upgrade.zod.ts', 'package-upgrade.test.ts']) {
+ expect(fs.existsSync(path.join(SRC_ROOT, 'kernel', f)), `kernel/${f} must be deleted`).toBe(false);
+ }
+ // Anti-vacuity: kept siblings prove the probe looks in the right place.
+ expect(fs.existsSync(path.join(SRC_ROOT, 'kernel', 'package-registry.zod.ts'))).toBe(true);
+ expect(fs.existsSync(path.join(SRC_ROOT, 'contracts', 'package-service.ts'))).toBe(true);
+
+ const importers: string[] = [];
+ const walk = (dir: string) => {
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) walk(full);
+ // This pin spells the specifier in its matcher fixtures, never as an import.
+ else if (entry.name.endsWith('.ts') && full !== fileURLToPath(import.meta.url)) {
+ const src = fs.readFileSync(full, 'utf-8');
+ if (/(?:import|export)[^;]*['"][^'"]*\/package-upgrade\.zod(?:\.js)?['"]/.test(src)) {
+ importers.push(path.relative(SRC_ROOT, full));
+ }
+ }
+ }
+ };
+ walk(SRC_ROOT);
+ expect(importers, 'a resurrected import means the retirement is being undone — nothing implemented or read the model').toEqual([]);
+ });
+
+ it('the generated shards no longer list any of the ten defs or the retired names', () => {
+ const shard = (dir: string, category: string) =>
+ fs.readFileSync(path.join(SPEC_ROOT, dir, `${category}.json`), 'utf-8');
+ const listed = (JSON.parse(shard('json-schema.manifest', 'kernel')) as { schemas: string[] }).schemas;
+ for (const def of RETIRED_DEFS) {
+ expect(listed, `${def} must have left json-schema.manifest/`).not.toContain(def);
+ }
+ // Anti-vacuity: surviving neighbours are still there.
+ expect(listed).toContain('kernel/PackageArtifact');
+ expect(listed).toContain('kernel/InstalledPackage');
+
+ // Word-bounded rather than quoted: the shards spell a row several ways.
+ const exact = (name: string) => new RegExp(`\\b${name}\\b`);
+ const controls: Record = { kernel: 'PackageArtifactSchema', contracts: 'IPackageService' };
+ for (const dir of ['api-surface', 'declaration-map', 'export-origins']) {
+ for (const category of ['kernel', 'contracts']) {
+ const file = path.join(SPEC_ROOT, dir, `${category}.json`);
+ if (!fs.existsSync(file)) continue; // declaration-map carries no contracts shard
+ const text = fs.readFileSync(file, 'utf-8');
+ for (const name of RETIRED_NAMES) {
+ expect(text, `${dir}/${category}.json must not list ${name}`).not.toMatch(exact(name));
+ }
+ expect(text, `${dir}/${category}.json anti-vacuity`).toMatch(exact(controls[category]!));
+ }
+ }
+ for (const dir of ['authorable-surface', 'authorable-defaults']) {
+ const text = shard(dir, 'kernel');
+ for (const def of RETIRED_DEFS) {
+ expect(text, `${dir}/ must carry no row under ${def}`).not.toMatch(new RegExp(`"${def}:`));
+ }
+ expect(text, `${dir}/ anti-vacuity`).toMatch(/"kernel\/PackageArtifact:/);
+ }
+ });
+});
+
+describe('ADR-0087 registration', () => {
+ it('declares all ten defs under major 18, with the D3 semantic entry wired and no D2 conversion', () => {
+ for (const def of RETIRED_DEFS) {
+ expect(RETIRED_DEFS_BY_MAJOR[18], `${def} must be declared`).toContain(def);
+ }
+ const step = MIGRATIONS_BY_MAJOR[18];
+ expect(step).toBeDefined();
+ const entry = step!.semantic.find((s) => s.id === SEMANTIC_ID);
+ expect(entry, `${SEMANTIC_ID} must be wired into the step-18 chain`).toBeDefined();
+ expect(entry!.reason).toMatch(/ADR-0049/);
+ expect(entry!.acceptanceCriteria).toMatch(/TS2305/);
+ // The route is stated where the next reader looks: why D3 semantic and not D2.
+ expect(entry!.reason).toMatch(/not a D2 conversion/);
+ // No backticks in `surface`: the upgrade guide renders it inside a code span AND a table cell.
+ expect(entry!.surface).not.toMatch(/`/);
+ for (const def of RETIRED_DEFS) expect(entry!.surface, `surface must name ${def}`).toContain(def);
+ for (const name of ['planUpgrade', 'getSnapshot', 'PlanUpgradeInput', 'PackageUpgradeResponse.data']) {
+ expect(entry!.surface, `surface must name ${name}`).toContain(name);
+ }
+ // The served neighbours are named as what to use, so no sweep takes them.
+ expect(entry!.replacement).toMatch(/RollbackToPackageCommitResponseSchema/);
+ expect(step!.rationale).toMatch(/retires the package upgrade model WHOLE/);
+
+ // Deliberately no mechanical conversion: a transform over a stack that
+ // never carries these shapes would be a seam that never runs.
+ const conversionIds = Object.values(CONVERSIONS_BY_MAJOR).flatMap((entries) => entries.map((c) => c.id));
+ expect(conversionIds.filter((id) => /package-upgrade|upgrade-plan|upgrade-snapshot/.test(id))).toEqual([]);
+ expect(step!.conversionIds.filter((id) => /package-upgrade|upgrade-plan|upgrade-snapshot/.test(id))).toEqual([]);
+ // Lit control: the conversion table is loaded and readable.
+ expect(conversionIds.length).toBeGreaterThan(10);
+ });
+
+ it('the `UpgradePlan.estimatedDuration` tombstone entries stay registered as history', () => {
+ expect(RETIRED_KEYS_BY_MAJOR[18]).toContain('kernel/UpgradePlan:estimatedDuration');
+ expect(MIGRATIONS_BY_MAJOR[18]!.semantic.map((s) => s.id)).toContain('kernel-package-lifecycle-durations-unit-in-key');
+ });
+});
+
+// ─── Tree-scoped absence, inside the radius already declared ─────────────────
+//
+// The walk reads the five repo roots `api/export-job-family-retirement.test.ts`
+// reads — `packages`, `examples`, `skills`, `content`, `scripts` — with the
+// same per-root extension sets; every one of those globs is already declared
+// for `@objectstack/spec` in `scripts/cross-package-test-inputs.mjs` and
+// mirrored in `turbo.json`, and this file is listed in `vitest.repo-tests.json`.
+//
+// The bound, stated: `docs/**`, `.claude/**`, `.github/**` and the repo-root
+// files are outside the walk (the one ADR sentence naming the file is corrected
+// in its own governed PR); `.tsx` under `packages/` and every code file under
+// `examples/` are not scanned, because a typed import of a retired name fails
+// `tsc` in its own package — the enforced channel there. The residue this leg
+// covers is what `tsc` does not compile: JSON, YAML, MD, MDX and untyped
+// `.js` / `.mjs` / `.cjs`.
+describe('tree-scoped absence: nothing inside the declared radius references a retired name', () => {
+ const REPO_ROOT = path.resolve(SPEC_ROOT, '../..');
+ const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/');
+
+ const WALK_ROOTS = ['packages', 'examples', 'skills', 'content', 'scripts'];
+ const SCANNED_EXT = new Set(['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs', '.json', '.md', '.mdx', '.yaml', '.yml']);
+ const EXAMPLES_EXT = new Set(['.json', '.md', '.mdx', '.yaml', '.yml']);
+ /** Build, SCM and cache state — not authored sources. */
+ const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']);
+
+ const NAME = new RegExp('\\b(' + RETIRED_NAMES.join('|') + ')\\b');
+ /**
+ * A reference, never a prose mention: an import/export specifier, a type
+ * position, a method call, a manifest def key, the module specifier, or the
+ * reference page's link.
+ */
+ const REFERENCE = new RegExp(
+ '(?:' +
+ '[{,]\\s*(?:type\\s+)?' + NAME.source + '\\s*[,}]' +
+ '|' +
+ '(?:typeof\\s+|:\\s*|<)' + NAME.source + '\\b' +
+ '|' +
+ NAME.source + '\\.\\w+\\(' +
+ '|' +
+ '"(?:' + RETIRED_DEFS.join('|') + ')"' +
+ '|' +
+ "['\"][^'\"]*/package-upgrade\\.zod(?:\\.js)?['\"]" +
+ '|' +
+ '/docs/references/kernel/package-upgrade\\b' +
+ ')',
+ );
+
+ /** Structural exclusions, each with its reason. NOT an allowlist file. */
+ const EXCLUDED = new Set([
+ // This pin names them to assert their absence.
+ THIS_FILE,
+ ]);
+ const EXCLUDED_PREFIXES = [
+ // Registers the retirement by def (entries + the generated registry).
+ 'packages/spec/src/migrations/',
+ // Generated projection of the registry.
+ 'packages/spec/spec-changes.json',
+ // `gen:schema`'s gitignored per-def output, which a local tree may still
+ // hold from a build before this retirement.
+ 'packages/spec/json-schema/',
+ // Written only by `gen:authorable-surface-base` (never a normal build); its
+ // `baseRev` is allowed to lag, and `check:authorable-surface` is the gate.
+ 'packages/spec/authorable-surface.base.json',
+ // Release prose records the removal (release-owned; never edited by a code PR).
+ 'content/docs/releases/',
+ '.changeset/',
+ ];
+ /** tsup's transient config bundle — a copy of `tsup.config.ts`, which the walk already reads. */
+ const TSUP_BUNDLED_CONFIG = /\.bundled_[^./]+\.mjs$/;
+
+ const readIfPresent = (full: string): string | undefined => {
+ try {
+ return fs.readFileSync(full, 'utf-8');
+ } catch (err) {
+ // A path that vanished mid-walk cannot be a surviving reference; every other fault is re-raised.
+ if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
+ return undefined;
+ }
+ };
+
+ it('the matcher recognises a reference and ignores a prose mention (anti-vacuity)', () => {
+ expect(REFERENCE.test("import { UpgradePlanSchema } from '@objectstack/spec/kernel';")).toBe(true);
+ expect(REFERENCE.test("import type { PlanUpgradeInput } from '@objectstack/spec/contracts';")).toBe(true);
+ expect(REFERENCE.test('const s: UpgradeSnapshot = {};')).toBe(true);
+ expect(REFERENCE.test('RollbackPackageRequestSchema.parse(value)')).toBe(true);
+ expect(REFERENCE.test('"kernel/UpgradePhase",')).toBe(true);
+ expect(REFERENCE.test("export * from './package-upgrade.zod';")).toBe(true);
+ expect(REFERENCE.test('see the [protocol](/docs/references/kernel/package-upgrade).')).toBe(true);
+ expect(REFERENCE.test('the `UpgradePlanSchema` export was removed')).toBe(false);
+ // The surviving neighbours are not matched.
+ expect(REFERENCE.test("import { PackageUpgradeRequestSchema, PackageUpgradeResponseSchema } from './package-api.zod';")).toBe(false);
+ expect(REFERENCE.test('const r: RollbackToPackageCommitResponse = x;')).toBe(false);
+ expect(REFERENCE.test('"kernel/PackageArtifact",')).toBe(false);
+ });
+
+ it('no reference survives inside the declared radius outside the retirement kit', () => {
+ const offenders: string[] = [];
+ let visited = 0;
+ // What git does not ignore, never the raw filesystem (`scripts/lib/repo-walk-testkit.ts`).
+ const skipDir = (name: string) => SKIPPED_DIRS.has(name) || name.startsWith('.');
+ for (const root of WALK_ROOTS) {
+ for (const full of listRepoWalkFiles(path.join(REPO_ROOT, root), { skipDir })) {
+ const rel = path.relative(REPO_ROOT, full).split(path.sep).join('/');
+ const name = path.basename(full);
+ if (!(rel.startsWith('examples/') ? EXAMPLES_EXT : SCANNED_EXT).has(path.extname(name))) continue;
+ if (name === 'CHANGELOG.md') continue; // release prose records the removal
+ if (EXCLUDED.has(rel) || EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue;
+ if (TSUP_BUNDLED_CONFIG.test(name)) continue;
+ visited += 1;
+ const text = readIfPresent(full);
+ if (text === undefined) continue;
+ const m = REFERENCE.exec(text);
+ if (m) offenders.push(`${rel} references \`${m[0].trim()}\``);
+ }
+ }
+ // Anti-vacuity: the walk really covered the tree.
+ expect(visited).toBeGreaterThan(1000);
+ expect(offenders, 'a reference to a retired name means the retirement is being undone — nothing implemented or read the model').toEqual([]);
+ });
+});
diff --git a/packages/spec/src/kernel/package-upgrade.test.ts b/packages/spec/src/kernel/package-upgrade.test.ts
deleted file mode 100644
index 0a9f6207062..00000000000
--- a/packages/spec/src/kernel/package-upgrade.test.ts
+++ /dev/null
@@ -1,314 +0,0 @@
-import { describe, it, expect } from 'vitest';
-import {
- MetadataChangeTypeSchema,
- MetadataDiffItemSchema,
- UpgradeImpactLevelSchema,
- UpgradePlanSchema,
- UpgradeSnapshotSchema,
- UpgradePackageRequestSchema,
- UpgradePhaseSchema,
- UpgradePackageResponseSchema,
- RollbackPackageRequestSchema,
- RollbackPackageResponseSchema,
-} from './package-upgrade.zod';
-
-const validManifest = {
- id: 'com.acme.crm',
- version: '1.0.0',
- type: 'app' as const,
- name: 'Acme CRM',
-};
-
-describe('MetadataChangeTypeSchema', () => {
- it('should accept valid change types', () => {
- const types = ['added', 'modified', 'removed', 'renamed'];
- types.forEach(type => {
- expect(() => MetadataChangeTypeSchema.parse(type)).not.toThrow();
- });
- });
-
- it('should reject invalid change types', () => {
- expect(() => MetadataChangeTypeSchema.parse('updated')).toThrow();
- });
-});
-
-describe('MetadataDiffItemSchema', () => {
- it('should accept valid diff item', () => {
- const diff = {
- type: 'object',
- name: 'account',
- changeType: 'modified' as const,
- summary: 'Added new field "priority"',
- };
- const parsed = MetadataDiffItemSchema.parse(diff);
- expect(parsed.hasConflict).toBe(false);
- });
-
- it('should accept diff item with conflict flag', () => {
- const diff = {
- type: 'view',
- name: 'account_list',
- changeType: 'modified' as const,
- hasConflict: true,
- summary: 'Column order changed — conflicts with customer customization',
- };
- const parsed = MetadataDiffItemSchema.parse(diff);
- expect(parsed.hasConflict).toBe(true);
- });
-
- it('should accept renamed item with previous name', () => {
- const diff = {
- type: 'object',
- name: 'contact_v2',
- changeType: 'renamed' as const,
- previousName: 'contact',
- };
- expect(() => MetadataDiffItemSchema.parse(diff)).not.toThrow();
- });
-});
-
-describe('UpgradeImpactLevelSchema', () => {
- it('should accept all impact levels', () => {
- const levels = ['none', 'low', 'medium', 'high', 'critical'];
- levels.forEach(level => {
- expect(() => UpgradeImpactLevelSchema.parse(level)).not.toThrow();
- });
- });
-});
-
-describe('UpgradePlanSchema', () => {
- it('should accept minimal upgrade plan', () => {
- const plan = {
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'medium' as const,
- changes: [],
- };
- const parsed = UpgradePlanSchema.parse(plan);
- expect(parsed.affectedCustomizations).toBe(0);
- expect(parsed.requiresMigration).toBe(false);
- });
-
- it('should accept full upgrade plan with changes and migrations', () => {
- const plan = {
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'high' as const,
- changes: [
- { type: 'object', name: 'account', changeType: 'modified' as const, hasConflict: true },
- { type: 'object', name: 'deal', changeType: 'added' as const },
- { type: 'view', name: 'lead_list', changeType: 'removed' as const },
- ],
- affectedCustomizations: 3,
- requiresMigration: true,
- migrationScripts: ['migrations/v2-add-deal.ts', 'migrations/v2-update-account.ts'],
- dependencyUpgrades: [
- { packageId: 'com.acme.base', fromVersion: '1.0.0', toVersion: '1.1.0' },
- ],
- estimatedDurationSeconds: 120,
- summary: 'Major upgrade adding Deals module and restructuring Account object',
- };
- const parsed = UpgradePlanSchema.parse(plan);
- expect(parsed.changes).toHaveLength(3);
- expect(parsed.requiresMigration).toBe(true);
- expect(parsed.dependencyUpgrades).toHaveLength(1);
- });
-});
-
-describe('UpgradeSnapshotSchema', () => {
- it('should accept valid snapshot', () => {
- const snapshot = {
- id: 'snap-001',
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- previousManifest: validManifest,
- metadataSnapshot: [
- { type: 'object', name: 'account', metadata: { label: 'Account' } },
- { type: 'view', name: 'account_list', metadata: { type: 'grid' } },
- ],
- createdAt: '2025-06-15T10:00:00Z',
- };
- const parsed = UpgradeSnapshotSchema.parse(snapshot);
- expect(parsed.metadataSnapshot).toHaveLength(2);
- });
-
- it('should accept snapshot with customizations backup', () => {
- const snapshot = {
- id: 'snap-002',
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- tenantId: 'tenant-001',
- previousManifest: validManifest,
- metadataSnapshot: [],
- customizationSnapshot: [
- { overlayId: 'overlay-001', patch: { label: 'Custom Label' } },
- ],
- createdAt: '2025-06-15T10:00:00Z',
- expiresAt: '2025-07-15T10:00:00Z',
- };
- const parsed = UpgradeSnapshotSchema.parse(snapshot);
- expect(parsed.customizationSnapshot).toHaveLength(1);
- });
-});
-
-describe('UpgradePackageRequestSchema', () => {
- it('should accept minimal upgrade request', () => {
- const request = {
- packageId: 'com.acme.crm',
- };
- const parsed = UpgradePackageRequestSchema.parse(request);
- expect(parsed.createSnapshot).toBe(true);
- expect(parsed.mergeStrategy).toBe('three-way-merge');
- expect(parsed.dryRun).toBe(false);
- expect(parsed.skipValidation).toBe(false);
- });
-
- it('should accept full upgrade request', () => {
- const request = {
- packageId: 'com.acme.crm',
- targetVersion: '2.0.0',
- manifest: { ...validManifest, version: '2.0.0' },
- createSnapshot: true,
- mergeStrategy: 'keep-custom' as const,
- dryRun: false,
- skipValidation: false,
- };
- const parsed = UpgradePackageRequestSchema.parse(request);
- expect(parsed.targetVersion).toBe('2.0.0');
- expect(parsed.mergeStrategy).toBe('keep-custom');
- });
-
- it('should accept dry-run request', () => {
- const request = {
- packageId: 'com.acme.crm',
- targetVersion: '2.0.0',
- dryRun: true,
- };
- const parsed = UpgradePackageRequestSchema.parse(request);
- expect(parsed.dryRun).toBe(true);
- });
-});
-
-describe('UpgradePhaseSchema', () => {
- it('should accept all upgrade phases', () => {
- const phases = [
- 'pending', 'analyzing', 'snapshot', 'executing',
- 'migrating', 'validating', 'completed', 'failed',
- 'rolling-back', 'rolled-back',
- ];
- phases.forEach(phase => {
- expect(() => UpgradePhaseSchema.parse(phase)).not.toThrow();
- });
- });
-});
-
-describe('UpgradePackageResponseSchema', () => {
- it('should accept successful upgrade response', () => {
- const response = {
- success: true,
- phase: 'completed' as const,
- snapshotId: 'snap-001',
- message: 'Package upgraded successfully from 1.0.0 to 2.0.0',
- };
- expect(() => UpgradePackageResponseSchema.parse(response)).not.toThrow();
- });
-
- it('should accept failed upgrade with conflicts', () => {
- const response = {
- success: false,
- phase: 'failed' as const,
- plan: {
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'high' as const,
- changes: [],
- },
- conflicts: [{
- path: 'fields.status.options',
- baseValue: ['a'],
- incomingValue: ['a', 'b'],
- customValue: ['a', 'c'],
- }],
- errorMessage: 'Upgrade failed: unresolved merge conflicts',
- };
- const parsed = UpgradePackageResponseSchema.parse(response);
- expect(parsed.success).toBe(false);
- expect(parsed.conflicts).toHaveLength(1);
- });
-});
-
-describe('RollbackPackageRequestSchema', () => {
- it('should accept valid rollback request', () => {
- const request = {
- packageId: 'com.acme.crm',
- snapshotId: 'snap-001',
- };
- const parsed = RollbackPackageRequestSchema.parse(request);
- expect(parsed.rollbackCustomizations).toBe(true);
- });
-
- it('should accept rollback without customization restore', () => {
- const request = {
- packageId: 'com.acme.crm',
- snapshotId: 'snap-001',
- rollbackCustomizations: false,
- };
- const parsed = RollbackPackageRequestSchema.parse(request);
- expect(parsed.rollbackCustomizations).toBe(false);
- });
-
- it('should reject missing required fields', () => {
- expect(() => RollbackPackageRequestSchema.parse({ packageId: 'test' })).toThrow();
- expect(() => RollbackPackageRequestSchema.parse({ snapshotId: 'test' })).toThrow();
- });
-});
-
-describe('RollbackPackageResponseSchema', () => {
- it('should accept successful rollback', () => {
- const response = {
- success: true,
- restoredVersion: '1.0.0',
- message: 'Rolled back to version 1.0.0',
- };
- const parsed = RollbackPackageResponseSchema.parse(response);
- expect(parsed.success).toBe(true);
- expect(parsed.restoredVersion).toBe('1.0.0');
- });
-});
-
-// #15678 (stack card 3/6 of #14478) — ruling B: the unit of a duration-shaped
-// number lives in the key NAME. The old spelling is a `retiredKey()` tombstone,
-// so the refusal carries the RENAME rather than a bare unrecognized-key error.
-describe('UpgradePlan.estimatedDuration carries its unit', () => {
- const basePlan = {
- packageId: 'com.acme.crm',
- fromVersion: '1.0.0',
- toVersion: '2.0.0',
- impactLevel: 'low' as const,
- changes: [],
- };
-
- it('REFUSES the retired `estimatedDuration` with the rename in the message', () => {
- const result = UpgradePlanSchema.safeParse({ ...basePlan, estimatedDuration: 120 });
- expect(result.success).toBe(false);
- const issue = result.error!.issues.find((i) => i.path.join('.') === 'estimatedDuration');
- expect(issue).toBeDefined();
- expect(issue!.code).not.toBe('unrecognized_keys');
- expect(issue!.message).toContain(
- '`UpgradePlan.estimatedDuration` was renamed to `estimatedDurationSeconds`',
- );
- // The prescription must name the unit, because SECONDS is the minority unit
- // in this package and the sibling rename on this card is milliseconds.
- expect(issue!.message).toContain('the value (seconds) is unchanged');
- });
-
- it('accepts `estimatedDurationSeconds` at the same magnitude', () => {
- const parsed = UpgradePlanSchema.parse({ ...basePlan, estimatedDurationSeconds: 120 });
- expect(parsed.estimatedDurationSeconds).toBe(120);
- });
-});
diff --git a/packages/spec/src/kernel/package-upgrade.zod.ts b/packages/spec/src/kernel/package-upgrade.zod.ts
deleted file mode 100644
index 5cbc747556b..00000000000
--- a/packages/spec/src/kernel/package-upgrade.zod.ts
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
-
-import { z } from 'zod';
-import { ManifestSchema } from './manifest.zod';
-
-/**
- * # Package Upgrade Protocol
- *
- * Defines the complete lifecycle for upgrading installed packages,
- * including pre-upgrade analysis, snapshot/backup, execution, validation,
- * and rollback capabilities.
- *
- * ## Architecture Alignment
- * - **Salesforce**: Managed Package upgrade with push upgrades and subscriber control
- * - **ServiceNow**: Update Sets with preview, commit, and back-out support
- * - **Helm**: Helm upgrade with rollback to previous release
- * - **Kubernetes**: Rolling update with readiness probes and automatic rollback
- *
- * ## Upgrade Flow
- * ```
- * 1. PreCheck → Validate compatibility, check dependencies
- * 2. Plan → Generate upgrade plan with metadata diff
- * 3. Snapshot → Backup current state (metadata + customizations)
- * 4. Execute → Apply new package metadata with 3-way merge
- * 5. Validate → Run post-upgrade health checks
- * 6. Commit → Finalize upgrade (or Rollback on failure)
- * ```
- */
-
-// ==========================================
-// Upgrade Plan & Analysis
-// ==========================================
-
-/**
- * Metadata Change Type
- * Type of change detected between package versions.
- */
-import { lazySchema } from '../shared/lazy-schema';
-import { retiredKey } from '../shared/retired-key';
-export const MetadataChangeTypeSchema = lazySchema(() => z.enum([
- 'added', // New metadata item added in new version
- 'modified', // Existing metadata item modified
- 'removed', // Metadata item removed in new version
- 'renamed', // Metadata item renamed
-]).describe('Type of metadata change between package versions'));
-
-/**
- * Metadata Diff Item
- * Describes a single metadata change between two package versions.
- */
-export const MetadataDiffItemSchema = lazySchema(() => z.object({
- /** Metadata type (e.g. "object", "view", "flow") */
- type: z.string().describe('Metadata type'),
-
- /** Metadata name */
- name: z.string().describe('Metadata name'),
-
- /** Type of change */
- changeType: MetadataChangeTypeSchema.describe('Category of metadata modification (added, modified, removed, or renamed)'),
-
- /** Whether this change has potential conflicts with customizations */
- hasConflict: z.boolean().default(false)
- .describe('Whether this change may conflict with customizations'),
-
- /** Human-readable summary of the change */
- summary: z.string().optional().describe('Human-readable change summary'),
-
- /** Previous name (for renames) */
- previousName: z.string().optional().describe('Previous name if renamed'),
-}).describe('Single metadata change between package versions'));
-
-/**
- * Upgrade Impact Level
- * Indicates the severity of impact the upgrade will have.
- */
-export const UpgradeImpactLevelSchema = lazySchema(() => z.enum([
- 'none', // No impact, seamless upgrade
- 'low', // Minor changes, no user action needed
- 'medium', // Some changes that may affect workflows
- 'high', // Significant changes, user review recommended
- 'critical', // Breaking changes, manual intervention required
-]).describe('Severity of upgrade impact'));
-
-/**
- * Upgrade Plan Schema
- * The analysis result before executing an upgrade.
- * Generated by comparing old version metadata with new version metadata.
- */
-export const UpgradePlanSchema = lazySchema(() => z.object({
- /** Package being upgraded */
- packageId: z.string().describe('Package identifier'),
-
- /** Current installed version */
- fromVersion: z.string().describe('Currently installed version'),
-
- /** Target version to upgrade to */
- toVersion: z.string().describe('Target upgrade version'),
-
- /** Overall impact level */
- impactLevel: UpgradeImpactLevelSchema.describe('Severity assessment from none (seamless) to critical (breaking changes)'),
-
- /** List of all metadata changes between versions */
- changes: z.array(MetadataDiffItemSchema).describe('All metadata changes'),
-
- /** Number of customer customizations that may be affected */
- affectedCustomizations: z.number().int().min(0).default(0)
- .describe('Count of customizations that may be affected'),
-
- /** Whether any migration scripts need to run */
- requiresMigration: z.boolean().default(false)
- .describe('Whether data migration scripts are needed'),
-
- /** Migration script paths (relative to package root) */
- migrationScripts: z.array(z.string()).optional()
- .describe('Paths to migration scripts'),
-
- /** Dependencies that also need upgrading */
- dependencyUpgrades: z.array(z.object({
- packageId: z.string(),
- fromVersion: z.string(),
- toVersion: z.string(),
- })).optional().describe('Dependent packages that also need upgrading'),
-
- /** Estimated upgrade duration in seconds */
- // Renamed from `estimatedDuration` (#15678, #14478 ruling B): the unit lived
- // only in the describe prose.
- estimatedDurationSeconds: z.number().int().min(0).optional()
- .describe('Estimated upgrade duration in seconds'),
-
- /** Tombstone for the rename above (#15678, ruling B on #14478). */
- estimatedDuration: retiredKey(
- '`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.',
- ),
-
- /** Human-readable summary */
- summary: z.string().optional().describe('Human-readable upgrade summary'),
-}).describe('Upgrade analysis plan generated before execution'));
-
-// ==========================================
-// Upgrade Snapshot (Pre-Upgrade Backup)
-// ==========================================
-
-/**
- * Upgrade Snapshot Schema
- * Captures the complete state before an upgrade for rollback capability.
- */
-export const UpgradeSnapshotSchema = lazySchema(() => z.object({
- /** Snapshot ID (UUID) */
- id: z.string().describe('Snapshot identifier'),
-
- /** Package being upgraded */
- packageId: z.string().describe('Package identifier'),
-
- /** Version being upgraded from */
- fromVersion: z.string().describe('Version before upgrade'),
-
- /** Version being upgraded to */
- toVersion: z.string().describe('Target upgrade version'),
-
- /** Tenant ID */
- tenantId: z.string().optional().describe('Tenant identifier'),
-
- /** Complete manifest of the old package version */
- previousManifest: ManifestSchema.describe('Complete manifest of the previous package version'),
-
- /**
- * Snapshot of all metadata records owned by this package.
- * Stored as array of { type, name, metadata } tuples.
- */
- metadataSnapshot: z.array(z.object({
- type: z.string(),
- name: z.string(),
- metadata: z.record(z.string(), z.unknown()),
- })).describe('Snapshot of all package metadata'),
-
- /**
- * Snapshot of all customer customizations (overlays) for this package's metadata.
- */
- customizationSnapshot: z.array(z.record(z.string(), z.unknown())).optional()
- .describe('Snapshot of customer customizations'),
-
- /** When the snapshot was created */
- createdAt: z.string().datetime().describe('Snapshot creation timestamp'),
-
- /** Expiry time for snapshot cleanup */
- expiresAt: z.string().datetime().optional().describe('Snapshot expiry timestamp'),
-}).describe('Pre-upgrade state snapshot for rollback capability'));
-
-// ==========================================
-// Upgrade Request/Response
-// ==========================================
-
-/**
- * Upgrade Package Request
- */
-export const UpgradePackageRequestSchema = lazySchema(() => z.object({
- /** Package ID to upgrade */
- packageId: z.string().describe('Package ID to upgrade'),
-
- /** Target version (if omitted, upgrades to latest) */
- targetVersion: z.string().optional().describe('Target version (defaults to latest)'),
-
- /** New manifest for the target version */
- manifest: ManifestSchema.optional().describe('New manifest (if installing from local)'),
-
- /** Whether to create a pre-upgrade snapshot */
- createSnapshot: z.boolean().default(true)
- .describe('Whether to create a pre-upgrade backup snapshot'),
-
- /** Merge strategy for handling customizations */
- mergeStrategy: z.enum([
- 'keep-custom',
- 'accept-incoming',
- 'three-way-merge',
- ]).default('three-way-merge').describe('How to handle customer customizations'),
-
- /** Whether to run in dry-run mode (preview only, no changes) */
- dryRun: z.boolean().default(false)
- .describe('Preview upgrade without making changes'),
-
- /** Whether to skip pre-upgrade validation */
- skipValidation: z.boolean().default(false)
- .describe('Skip pre-upgrade compatibility checks'),
-}).describe('Upgrade package request'));
-
-/**
- * Upgrade Phase
- * Current phase of the upgrade process.
- */
-export const UpgradePhaseSchema = lazySchema(() => z.enum([
- 'pending', // Upgrade requested but not started
- 'analyzing', // Generating upgrade plan
- 'snapshot', // Creating pre-upgrade snapshot
- 'executing', // Applying metadata changes
- 'migrating', // Running migration scripts
- 'validating', // Post-upgrade validation
- 'completed', // Upgrade completed successfully
- 'failed', // Upgrade failed
- 'rolling-back', // Rollback in progress
- 'rolled-back', // Rollback completed
-]).describe('Current phase of the upgrade process'));
-
-/**
- * Upgrade Package Response
- */
-export const UpgradePackageResponseSchema = lazySchema(() => z.object({
- /** Whether the upgrade was successful */
- success: z.boolean().describe('Whether the upgrade succeeded'),
-
- /** Current upgrade phase */
- phase: UpgradePhaseSchema.describe('Current upgrade phase'),
-
- /** The upgrade plan that was executed */
- plan: UpgradePlanSchema.optional().describe('Upgrade plan'),
-
- /** Snapshot ID for rollback */
- snapshotId: z.string().optional().describe('Snapshot ID for rollback'),
-
- /** Merge conflicts that need manual resolution (if any) */
- conflicts: z.array(z.object({
- path: z.string(),
- baseValue: z.unknown(),
- incomingValue: z.unknown(),
- customValue: z.unknown(),
- })).optional().describe('Unresolved merge conflicts'),
-
- /** Error message (if failed) */
- errorMessage: z.string().optional().describe('Error message if upgrade failed'),
-
- /** Human-readable summary */
- message: z.string().optional().describe('Human-readable status message'),
-}).describe('Upgrade package response'));
-
-// ==========================================
-// Rollback
-// ==========================================
-
-/**
- * Rollback Package Request
- */
-export const RollbackPackageRequestSchema = lazySchema(() => z.object({
- /** Package ID to rollback */
- packageId: z.string().describe('Package ID to rollback'),
-
- /** Snapshot ID to restore from */
- snapshotId: z.string().describe('Snapshot ID to restore from'),
-
- /** Whether to also rollback customizations */
- rollbackCustomizations: z.boolean().default(true)
- .describe('Whether to restore pre-upgrade customizations'),
-}).describe('Rollback package request'));
-
-/**
- * Rollback Package Response
- */
-export const RollbackPackageResponseSchema = lazySchema(() => z.object({
- /** Whether the rollback was successful */
- success: z.boolean().describe('Whether the rollback succeeded'),
-
- /** Restored version */
- restoredVersion: z.string().optional().describe('Version restored to'),
-
- /** Message */
- message: z.string().optional().describe('Rollback status message'),
-}).describe('Rollback package response'));
-
-// ==========================================
-// Export Types
-// ==========================================
-
-export type MetadataChangeType = z.input;
-export type MetadataDiffItem = z.input;
-/** Post-parse shape of {@link MetadataDiffItem} — defaults applied, transforms run (ADR-0122). */
-export type MetadataDiffItemParsed = z.infer;
-export type UpgradeImpactLevel = z.input;
-export type UpgradePlan = z.input;
-/** Post-parse shape of {@link UpgradePlan} — defaults applied, transforms run (ADR-0122). */
-export type UpgradePlanParsed = z.infer;
-export type UpgradeSnapshot = z.input;
-/** Post-parse shape of {@link UpgradeSnapshot} — defaults applied, transforms run (ADR-0122). */
-export type UpgradeSnapshotParsed = z.infer;
-export type UpgradePackageRequest = z.input;
-/** Post-parse shape of {@link UpgradePackageRequest} — defaults applied, transforms run (ADR-0122). */
-export type UpgradePackageRequestParsed = z.infer;
-export type UpgradePhase = z.input;
-export type UpgradePackageResponse = z.input;
-/** Post-parse shape of {@link UpgradePackageResponse} — defaults applied, transforms run (ADR-0122). */
-export type UpgradePackageResponseParsed = z.infer;
-export type RollbackPackageRequest = z.input;
-/** Post-parse shape of {@link RollbackPackageRequest} — defaults applied, transforms run (ADR-0122). */
-export type RollbackPackageRequestParsed = z.infer;
-export type RollbackPackageResponse = z.input;
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataChangeType.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataChangeType.ts
new file mode 100644
index 00000000000..cfb76a687e0
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataChangeType.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/MetadataChangeType`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the `added / modified / removed / renamed` enum an upgrade diff tagged each change with.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/MetadataChangeType';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataDiffItem.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataDiffItem.ts
new file mode 100644
index 00000000000..1d74e4a2946
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__MetadataDiffItem.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/MetadataDiffItem`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared one row of the version-to-version metadata diff an upgrade plan carried.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/MetadataDiffItem';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageRequest.ts
new file mode 100644
index 00000000000..d0fdcbc062a
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageRequest.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/RollbackPackageRequest`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the request body of the snapshot rollback.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/RollbackPackageRequest';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageResponse.ts
new file mode 100644
index 00000000000..2ce83cb0a7e
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__RollbackPackageResponse.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/RollbackPackageResponse`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the answer of the snapshot rollback.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/RollbackPackageResponse';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeImpactLevel.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeImpactLevel.ts
new file mode 100644
index 00000000000..f95854474da
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeImpactLevel.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradeImpactLevel`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the `none … critical` severity an upgrade plan graded itself with.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradeImpactLevel';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageRequest.ts
new file mode 100644
index 00000000000..2857f4b7ebf
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageRequest.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradePackageRequest`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the request body of the unserved package upgrade.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradePackageRequest';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageResponse.ts
new file mode 100644
index 00000000000..246578e2c53
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePackageResponse.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradePackageResponse`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the answer of the unserved package upgrade, with its merge `conflicts`.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradePackageResponse';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePhase.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePhase.ts
new file mode 100644
index 00000000000..fb5eaebe04f
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePhase.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradePhase`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the `pending … rolled-back` phase vocabulary of an upgrade run.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradePhase';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePlan.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePlan.ts
new file mode 100644
index 00000000000..032a0fab025
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradePlan.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradePlan`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the pre-upgrade analysis `IPackageService.planUpgrade` would have returned.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradePlan';
diff --git a/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeSnapshot.ts b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeSnapshot.ts
new file mode 100644
index 00000000000..d54f238ae46
--- /dev/null
+++ b/packages/spec/src/migrations/entries/retired-defs/18.kernel__UpgradeSnapshot.ts
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+// #25608 — `kernel/UpgradeSnapshot`, retired whole with `kernel/package-upgrade.zod.ts`
+// (ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the
+// package lifecycle classes, item 3). It declared the pre-upgrade backup a rollback would have restored from.
+// Zero readers in objectstack, objectui and hotcrm. No carrier key and no
+// authored document, so no tombstone and no D2 conversion — this table plus
+// the D3 semantic entry `kernel-package-upgrade-family-retired` are the
+// declaration.
+export const entry = 'kernel/UpgradeSnapshot';
diff --git a/packages/spec/src/migrations/entries/semantic/18.kernel-package-upgrade-family-retired.ts b/packages/spec/src/migrations/entries/semantic/18.kernel-package-upgrade-family-retired.ts
new file mode 100644
index 00000000000..0b214d8bd44
--- /dev/null
+++ b/packages/spec/src/migrations/entries/semantic/18.kernel-package-upgrade-family-retired.ts
@@ -0,0 +1,66 @@
+// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
+
+import type { SemanticMigration } from '../../types.js';
+
+export const entry: SemanticMigration = {
+ id: 'kernel-package-upgrade-family-retired',
+ // No backticks in `surface` — build-upgrade-guide.ts renders it inside a
+ // code span AND a table cell.
+ surface:
+ 'the package upgrade model, retired whole: the ten defs kernel/MetadataChangeType, '
+ + 'kernel/MetadataDiffItem, kernel/UpgradeImpactLevel, kernel/UpgradePlan, '
+ + 'kernel/UpgradeSnapshot, kernel/UpgradePackageRequest, kernel/UpgradePhase, '
+ + 'kernel/UpgradePackageResponse, kernel/RollbackPackageRequest and '
+ + 'kernel/RollbackPackageResponse, and every name kernel/package-upgrade.zod.ts exported from '
+ + '@objectstack/spec/kernel (the ten *Schema consts, their z.input aliases and their *Parsed '
+ + 'aliases); the IPackageService members planUpgrade, upgrade, rollback and getSnapshot with '
+ + 'their inputs PlanUpgradeInput, ExecuteUpgradeInput and RollbackInput from '
+ + '@objectstack/spec/contracts; and the plan member of PackageUpgradeResponse.data from '
+ + '@objectstack/spec/api',
+ replacement:
+ 'nothing to re-declare — no package upgrade engine exists on the platform, so there is no '
+ + 'working configuration to migrate to. Nothing generated an upgrade plan, diffed two package '
+ + 'versions, took a pre-upgrade snapshot, three-way merged a customization or rolled a package '
+ + 'back to a snapshot. What the platform does serve: an upgrade is a re-install of the new '
+ + 'version (`POST /api/v1/packages` with `overwrite: true`), and the package rollback it '
+ + 'serves is the commit rollback (`POST /api/v1/packages/:id/rollback` with a `commitId`, '
+ + 'answered by `RollbackToPackageCommitResponseSchema` in `@objectstack/spec/api`). '
+ + '`PackageUpgradeRequestSchema` and `PackageUpgradeResponseSchema` stay published, bound to no '
+ + 'route, minus the response\'s `plan`. If a package upgrade becomes a product capability it '
+ + 're-declares fresh, through the enforce route of ADR-0049 — the engine first, the '
+ + 'vocabulary second',
+ reason:
+ 'ADR-0049 enforce-or-remove; maintainer ruling A of 2026-10-19 on the package lifecycle '
+ + 'classes, item 3 ("Retired: packages/spec/src/kernel/package-upgrade.zod.ts (plan, diff, '
+ + 'conflict, snapshot, rollback; zero runtime consumers), with its ADR-0087 conversion"), done '
+ + 'directly in 18.x with no retirement stage. Ten defs sat on the exported surface and in the '
+ + 'generated reference docs, and were read by NOTHING: the schemas were exported from '
+ + '`@objectstack/spec/kernel`, mounted by no `stack.zod.ts` key, registered as no metadata '
+ + 'type, and no provider ever implemented the four `IPackageService` upgrade members that '
+ + 'returned them; the reader census over every package outside `packages/spec`, over '
+ + '`examples/**` and `skills/**`, and over objectui and hotcrm returned zero hits for every '
+ + 'exported name, with a lit control. `UpgradePackageRequest.createSnapshot` and '
+ + '`mergeStrategy: three-way-merge` read as guarantees the platform kept, and neither ever '
+ + 'did — the declared-but-unenforced shape on the one surface an operator trusts to protect '
+ + 'their customizations. The `UpgradePlan.estimatedDuration` tombstone of the duration-unit '
+ + 'ruling (`RETIRED_KEYS_BY_MAJOR[18]`, D3 `kernel-package-lifecycle-durations-unit-in-key`) '
+ + 'leaves with its def\'s source; its registry entries stay as history. Why D3 semantic and '
+ + 'not a D2 conversion: the chain walks a normalized STACK and `applyConversionsToStoredItem` '
+ + 'maps a metadata type onto one of its collections; none of these schemas is either, so a '
+ + 'conversion would be a transform with no seam that ever runs, and with no carrier key there '
+ + 'is no shape on which a tombstone could sit. ADR-0087.',
+ acceptanceCriteria:
+ 'No code imports MetadataChangeTypeSchema, MetadataDiffItemSchema, UpgradeImpactLevelSchema, '
+ + 'UpgradePlanSchema, UpgradeSnapshotSchema, UpgradePackageRequestSchema, UpgradePhaseSchema, '
+ + 'UpgradePackageResponseSchema, RollbackPackageRequestSchema or RollbackPackageResponseSchema '
+ + '— or any of their type aliases — from @objectstack/spec or @objectstack/spec/kernel, and '
+ + 'none imports PlanUpgradeInput, ExecuteUpgradeInput or RollbackInput from '
+ + '@objectstack/spec/contracts: every such import is TS2305 after upgrade. No IPackageService '
+ + 'implementation still declares planUpgrade, upgrade, rollback or getSnapshot (the contract '
+ + 'no longer carries them, so no caller typed against it can reach them), and no reader of '
+ + 'PackageUpgradeResponse.data.plan remains (TS2339). The ten defs are absent from '
+ + '`json-schema.manifest/kernel.json`, the api-surface / declaration-map / export-origins '
+ + 'shards and the generated reference docs. ⚠️ Runtime behaviour is deliberately UNCHANGED '
+ + 'and must be verified as such: nothing ever parsed or read these shapes, so removing them '
+ + 'removes no behaviour.',
+};
diff --git a/packages/spec/src/migrations/registry.ts.template b/packages/spec/src/migrations/registry.ts.template
index ccc3ba450b7..0bb08269cd6 100644
--- a/packages/spec/src/migrations/registry.ts.template
+++ b/packages/spec/src/migrations/registry.ts.template
@@ -2201,6 +2201,21 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [
+ 'no tombstone, and no D2 conversion exists: a refused hook carries no intent a rewrite could '
+ 'keep. Its D3 record is the semantic entry `hook-body-stored-metadata-target-refused`.',
},
+ {
+ id: 'kernel-package-upgrade-family-retired',
+ order: 109,
+ text:
+ 'It also retires the package upgrade model WHOLE (ADR-0049 enforce-or-remove; maintainer '
+ + 'ruling A of 2026-10-19 on the package lifecycle classes, item 3): the ten defs of '
+ + '`kernel/package-upgrade.zod.ts` — the upgrade plan, its metadata diff and impact level, the '
+ + 'pre-upgrade snapshot, the upgrade and rollback request/response pairs and the phase '
+ + 'vocabulary — exported from `@objectstack/spec/kernel`, mounted by no stack key, registered '
+ + 'as no metadata type and read by nothing in this repository, objectui or hotcrm, leave via '
+ + 'RETIRED_DEFS_BY_MAJOR with one D3 semantic entry, together with the four `IPackageService` '
+ + 'upgrade members no provider implemented and the `plan` member of the unrouted '
+ + '`PackageUpgradeResponse`. The `UpgradePlan.estimatedDuration` tombstone leaves with its '
+ + 'def\'s source and its RETIRED_KEYS_BY_MAJOR[18] entry stays as history.',
+ },
{
id: 'list-view-filterable-fields-retired',
order: 105,
diff --git a/packages/spec/src/system/compliance-families-retirement.test.ts b/packages/spec/src/system/compliance-families-retirement.test.ts
index 81ae759e4b5..d9500ff8f55 100644
--- a/packages/spec/src/system/compliance-families-retirement.test.ts
+++ b/packages/spec/src/system/compliance-families-retirement.test.ts
@@ -140,9 +140,11 @@ const HISTORY_SEMANTIC_IDS = [
/**
* The near-namesakes a "finish everything incident / change" sweep would
- * plausibly take, each a DIFFERENT declaration with a live consumer.
+ * plausibly take, each a DIFFERENT declaration that stays published. (It was
+ * `MetadataChangeType` until #25608 retired `kernel/package-upgrade.zod.ts`
+ * whole; its only reader was that module itself.)
*/
-const MUST_SURVIVE_KERNEL = ['MetadataChangeTypeSchema', 'MetadataChangeType'] as const;
+const MUST_SURVIVE_KERNEL = ['BreakingChangeSchema', 'BreakingChange'] as const;
/** System-entry neighbours that stay — the ones the retired files imported from. */
const MUST_SURVIVE_SYSTEM = ['DataClassificationSchema', 'ComplianceFrameworkSchema', 'ChangeSetSchema'] as const;
@@ -429,7 +431,7 @@ describe('tree-scoped absence: nothing inside the declared radius references a r
expect(REFERENCE.test('"system/RollbackPlan",')).toBe(true);
expect(REFERENCE.test('the `IncidentResponsePolicySchema` export was removed')).toBe(false);
expect(REFERENCE.test("title: 'Incident push failed: {record.name}'")).toBe(false);
- expect(REFERENCE.test('MetadataChangeTypeSchema')).toBe(false); // the live near-namesake
+ expect(REFERENCE.test('BreakingChangeSchema')).toBe(false); // the surviving near-namesake
});
it('the build-detritus exclusion is NARROW — it names tsup\'s bundle and nothing authored', () => {
diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts
index a9971f38565..a81d569bda7 100644
--- a/packages/spec/src/type-alias-convention.pin.test.ts
+++ b/packages/spec/src/type-alias-convention.pin.test.ts
@@ -162,7 +162,6 @@ import type * as M88 from './kernel/metadata-plugin.zod.js';
import type * as M89 from './kernel/metadata-protection.zod.js';
import type * as M90 from './kernel/package-artifact.zod.js';
import type * as M91 from './kernel/package-registry.zod.js';
-import type * as M92 from './kernel/package-upgrade.zod.js';
import type * as M93 from './kernel/plugin-capability.zod.js';
import type * as M94 from './kernel/plugin-lifecycle-advanced.zod.js';
import type * as M95 from './kernel/plugin-loading.zod.js';
@@ -279,7 +278,7 @@ import type * as M190 from './kernel/execution-context.zod.js';
import type * as M191 from './automation/api-trigger-secret-ref.zod.js';
// ---------------------------------------------------------------------------
-// 790 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared.
+// 786 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
@@ -1024,12 +1023,6 @@ export type Iso_kernel_packageRegistry__PackageStatusEnum = Assert, z.infer< typeof M91.UninstallPackageRequestSchema > >>;
export type Iso_kernel_packageRegistry__UninstallPackageResponseSchema = Assert, z.infer< typeof M91.UninstallPackageResponseSchema > >>;
-// kernel/package-upgrade.zod.ts
-export type Iso_kernel_packageUpgrade__MetadataChangeTypeSchema = Assert, z.infer< typeof M92.MetadataChangeTypeSchema > >>;
-export type Iso_kernel_packageUpgrade__RollbackPackageResponseSchema = Assert, z.infer< typeof M92.RollbackPackageResponseSchema > >>;
-export type Iso_kernel_packageUpgrade__UpgradeImpactLevelSchema = Assert, z.infer< typeof M92.UpgradeImpactLevelSchema > >>;
-export type Iso_kernel_packageUpgrade__UpgradePhaseSchema = Assert, z.infer< typeof M92.UpgradePhaseSchema > >>;
-
// kernel/plugin-capability.zod.ts
export type Iso_kernel_pluginCapability__CapabilityConformanceLevelSchema = Assert, z.infer< typeof M93.CapabilityConformanceLevelSchema > >>;
export type Iso_kernel_pluginCapability__ProtocolReferenceSchema = Assert, z.infer< typeof M93.ProtocolReferenceSchema > >>;
@@ -1708,7 +1701,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 790 isomorphic pins', () => {
+ it('still declares all 786 isomorphic pins', () => {
// The truth of each pin is proved by tsc, not here — an `Assert>`
// 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
@@ -2573,7 +2566,18 @@ describe('ADR-0122 type-alias convention', () => {
// start node gives its hook secret — a plain string, so it is pinned here
// (Iso_automation_apiTriggerSecretRef__ApiTriggerSecretRefSchema) rather
// than given a `…Parsed` synonym. +1 added.
- expect(pins).toHaveLength(790);
+ //
+ // 790 -> 786 is #25608's retirement of `kernel/package-upgrade.zod.ts`
+ // whole (ADR-0049 enforce-or-remove, `RETIRED_DEFS_BY_MAJOR[18]`): four of
+ // its schemas were isomorphic and pinned here —
+ // Iso_kernel_packageUpgrade__MetadataChangeTypeSchema,
+ // Iso_kernel_packageUpgrade__RollbackPackageResponseSchema,
+ // Iso_kernel_packageUpgrade__UpgradeImpactLevelSchema and
+ // Iso_kernel_packageUpgrade__UpgradePhaseSchema — and they leave with the
+ // module, its `M92` import with them (the slot stays empty; the numbering
+ // is a label, not a count). The module's other defs each carried an
+ // `XParsed` alias and were never on this list. -4 removed.
+ expect(pins).toHaveLength(786);
// The count is stated in PROSE twice as well — this case's title and the
// section header above the pin list — and until commit c6b05c76a nothing read either
diff --git a/packages/spec/vitest.repo-tests.json b/packages/spec/vitest.repo-tests.json
index 333c6bf50d7..085a091478e 100644
--- a/packages/spec/vitest.repo-tests.json
+++ b/packages/spec/vitest.repo-tests.json
@@ -52,6 +52,7 @@
"src/integration/connector-sync-retirement.test.ts",
"src/integration/connector-triggers-retirement.test.ts",
"src/kernel/metadata-type-registry-allow-org-override-absence.test.ts",
+ "src/kernel/package-upgrade-family-retirement.test.ts",
"src/qa/requires-plugins-retirement.test.ts",
"src/security/admin-scope-binding-authoring-retirement.test.ts",
"src/security/permission-set-managed-by-retirement.test.ts",