dotkernel-evolution-pattern
Use whenever a change would break something a client already depends on in a Dotkernel project — renaming or removing a field, parameter, route or template variable; changing a type, status code or re
SKILL.md.claude/skills/dotkernel-evolution-pattern/SKILL.md
GitHubEvolution pattern, not versioning
Applies to
Projects: - Dotkernel API
- Dotkernel Admin
- Dotkernel Frontend
- Dotkernel Light
Packages: - none
Dotkernel prefers evolving one codebase over maintaining parallel versions. Breaking changes are
announced ahead of time and removed on a published date. Full rewrites and /v2/ prefixes are a
last resort reserved for architectural change.
Default answer to "should we add /v2?": no. Propose an evolution path first. Escalate to versioning only for genuinely architectural change (a different protocol or response paradigm), and say explicitly that you are recommending an exception.
Scope note: the mechanics below (Sunset headers) are an API concern. In a templated project the same thinking applies to route URLs, query parameters and anything a bookmark, integration or email link depends on — but the announcement channel is a redirect and a changelog entry, not a header.
Decision table
| Change | Approach |
|---|---|
| Fix a typo in a parameter name | Accept both for a period, deprecate the old. Versioning is overkill. |
| Add a response field | Just add it — additive changes are non-breaking. |
| Add a request parameter | Make it optional with a safe default. |
| Richer behaviour for richer input | Branch on the payload: old shape → old behaviour, new shape → enhanced. |
| Remove a field, change a type, tighten validation | Breaking. Deprecate with a sunset date, keep the old path until then. |
| Remove an endpoint or route | Deprecate with a sunset date; afterwards it answers 410 Gone (API) or permanently redirects (templated), and 404s once removed. |
| Move a page to a new URL (templated) | Serve it at the new URL and permanently redirect the old one straight away — the redirect is what keeps bookmarks and email links working. |
| Rework one endpoint fundamentally | A v2 for that endpoint only, never the whole project. |
| A different response paradigm entirely | Genuine versioning. |
Old and new coexist for a reasonable window. The sunset date is a promise — do not shorten it silently.
Marking a deprecation (API-style projects)
Class-level attribute on the handler:
#[ResourceDeprecation(
sunset: '2099-01-01', // placeholder — use the real, future date agreed for this window
link: 'https://docs.dotkernel.org/…/api-evolution/',
deprecationReason: 'Replaced by GET /book/{id}/detail.',
rel: 'sunset',
type: 'text/html',
)]
class GetBookResourceHandler extends AbstractHandler
Mechanics:
- The deprecation middleware reflects the handler after dispatch and adds
Sunset:andLink:response headers. - Once the sunset date has passed, the middleware throws
ExpiredExceptionbefore the handler runs, and the endpoint answers410 Goneas problem details. sunsetmust be a valid date or the attribute constructor throws. It must also be in the future: a past date deprecates and kills the endpoint in the same change.- If
linkis omitted, the middleware falls back to the documentation URL inapplication.versioningconfig.
Resulting response:
HTTP/1.1 200 OK
Sunset: 2099-01-01
Link: https://docs.dotkernel.org/…;rel="sunset";type="text/html"
Obligations that come with a deprecation
The attribute compiling is not "done". Also:
- Mark the operation
deprecated: truein the module'sOpenAPI.phpand name the replacement. - Update the documentation page describing the change and the recommended client update.
- Note it in
CHANGELOG.md(only if the user asks you to touch the changelog). - Add or extend a test asserting the
Sunsetheader. - Keep the old path working until the sunset date. Do not delete in the same change.
- Announce through the channels the project actually uses — headers, docs, release notes, newsletter — early enough for third-party developers to plan.
After sunset the endpoint already answers 410, so removal is a clean-up, not an emergency. Remove the handler, its route, its authorization entry, its presentation metadata and its documentation block in one later change: a removed endpoint should 404, not 500.
Anti-patterns to flag
/v2/...route groups,?version=2, or versioned media types added to a codebase that has chosen evolution.- Renaming a response field in place "because the old name was wrong".
- Making an existing optional field required.
- Narrowing an enum or tightening a validator on a live route with no sunset window.
- Changing a success status code (200 → 204) on a live endpoint.
- Changing a route URL in a templated project without a permanent redirect from the old one.
If asked to do one of these, do it only after stating the breaking-change consequence and offering the evolution alternative.