- name
- migrate-service
- description
- Migrate one existing Ghost Core service to the boot-owned defineService pattern. Use when asked to migrate or prepare an individual service, including its dependency wiring, consumers, inventory and enforcement. Establish coverage first and keep the initializer migration to one service and one commit.
# Migrate one service
Prepare a small, behavior-preserving migration of one existing service. This is
an execution workflow, not the final service-authoring guide or permission to
redesign a service's lifecycle. Read current repository guidance rather than
copying an earlier migration blindly.
## 1. Establish the starting point
- Read the root and nearest `AGENTS.md`, the
[services guide](../../../ghost/core/server/services/README.md),
[kernel contract](../../../ghost/core/kernel/README.md#service-initialization),
[testing guide](../../../docs/contributing/testing.md) and
[contribution workflow](../../../docs/contributing/workflow.md).
- Inspect status, existing branches/worktrees and related PRs. Preserve unrelated
work. Fetch `origin` and start from current `origin/main`; refresh dependencies
with `pnpm bootstrap` in a fresh checkout or after dependency changes.
- Read this service's entry in
`ghost/core/server/services/service-inventory.yaml` and any existing migration
plan. If a plan-registry skill is available, use it to find/update that plan;
do not create a competing plan in the repository.
- Verify prerequisites are merged. An open tests or enforcement PR is not main.
Preparation can proceed against a proposed policy, but record its exact head
and repeat validation against the policy that actually merges.
## 2. Audit the whole service and its callers
Use `rg` across production and tests. Include deep imports, nested helpers,
separate actions, job handlers, CLI use and CommonJS consumers. Record:
- Existing runtime/type exports and the intended instance and extras surfaces.
- Who constructs each instance and whether consumers capture it during import.
- Dependencies acquired at import, boot, request, dispatch and job execution.
- Configuration captured once versus read again for each operation.
- Shared identity, caches, pending/rejected promises and invalidation semantics.
- Resource ownership, partial-startup cleanup, normal shutdown and failure policy.
Confirm the capability belongs in services. A reusable constructor or helper
without an application-owned instance belongs elsewhere; do not add an empty
initializer merely to keep its directory.
Read `.dependency-cruiser.cjs` and `.oxlintrc.json` on the actual base. Where the
service DI ratchet is present, its scope includes every implementation file in
the directory, not just the main class. Only the top-level wiring `index.ts` is
exempt. Implementations must not retrieve collaborators through their own root.
Wire application instances, models, logging and configured adapters in the root.
Keep approved libraries imported where used and use explicit type-only imports.
Review any new exact library allowance with its reason; do not widen directories,
inject stateless utilities to evade the policy, or duplicate shared helpers.
Preserve late acquisition with getters/factories when dependencies are unavailable
at boot. If audit scripts exist, inspect their detailed output: findings may not
make the audit command fail, and a summary may omit unresolved/own-root imports.
## 3. Establish coverage before changing production
Map each proposed change to an existing behavioral assertion. Run the relevant
unchanged root/implementation tests and real consumer checks on the base.
Initialization complexity, an audited inventory entry or an old green result
is not proof of coverage.
Cover the real risk: retained consumers, configuration timing, transaction or
cache behavior, boot ordering and resource cleanup. Use existing cold-boot
HTTP/job coverage where it already proves readiness. Do not copy a boot harness
for every service or duplicate generic kernel concurrency/retry tests.
If a needed assertion is absent, prepare passing characterization against the
old implementation in a tests-only PR and merge it first. Keep one commit per
service in preparatory batches. If a test exposes a production bug, isolate the
fix ahead of initializer adoption; do not change expectations to hide it.
Substantial dependency refactors, fixture rewrites and business changes are also
preparation work, separate from the minimal initializer PR. Preserve the old
public API during preparation. Small dependency wiring can accompany migration
when existing tests protect it.
## 4. Make the smallest initializer change
- Create a TypeScript top-level `index.ts` default-exporting `defineService`
from the kernel. Return one fully ready instance from its factory.
- Expose normal operations on `.service`. Keep classes and implementation
contracts in implementation files; root named exports are type-only.
- Use optional `extras` only for justified transitional concerns. Catalogue each
helper's purpose, callers, timing, dependencies, failure policy and destination.
Extras are not automatic lifecycle hooks or a substitute for readiness.
- Boot calls and **awaits** `init()` in the appropriate phase, even for a
synchronous factory. Initialize before eager consumers read `.service`.
Importing a root early is fine; evaluating its guarded getter early is not.
- Adapt every production consumer. Retain CommonJS `.default` at existing
boundaries; do not convert boot wholesale or add a wrapper solely for it.
- Preserve receivers when forwarding methods. Keep validation at the same public
boundary and preserve synchronous throws versus promise rejections where
callers depend on that distinction.
- Keep config reads, external operations, jobs and per-operation resources at
their established times. Do not make the first request initialize the service.
- If startup acquires resources, clean up that attempt before rejecting. Do not
dispose borrowed shared resources or change normal shutdown ownership.
If the service requires new kernel features, a restart/replacement policy, an API
redesign or broad boot orchestration, record the concrete mismatch and separate
that decision from the small migration. Do not silently expand its scope.
Migration tests may receive mechanical import/type/stub and initializer-await
adaptations. Add no new test file/case in the migration commit; new assertions
or substantial harness changes return to preparation. Never weaken assertions,
coverage gates, lint or types to make the refactor pass.
## 5. Update metadata and verify
Update only this service's inventory entry: `migrated`, entry points, evidence,
contracts, extras and remaining gaps. Preserve historical audit provenance;
record the inspected base and distinguish old evidence from new validation.
Do not restore manual totals or erase unrelated backlog items.
Opt the root into `ghost/service-root-shape` in `.oxlintrc.json` as required by
the current configuration; the tooling scope check must agree with the inventory.
Once the DI ratchet is present, `migrated: true` also opts implementations into
its normal boundary checks. Do not claim enforcement passed if that policy was
only checked in a temporary integration or is still awaiting merge.
Run focused tests before/after, relevant real consumer/DB checks, root-shape and
inventory validation, lint/typechecks and the default `pnpm check`. Run one full
local validation at a time. Investigate failures from fresh output; a passing
retry or an unchanged file alone does not establish that a failure belongs to
main. Keep unrelated fixes out of the migration.
Review the final diff: one service, unchanged operation behavior, no hidden
composition, no new tests, and only justified metadata/enforcement changes.
Use the repository commit skill and make **one commit for this service**.
Keep unrelated tooling/skill changes on their own branch. Report the branch,
commit, validation and unresolved prerequisites. Push/open a PR only within the
user's authorization, rebase on fetched main before opening, and report current
CI/review state accurately. Do not merge or enable auto-merge implicitly.
Voir sur GitHub