Contrib Module Adoption and Retirement¶
The repeatable method for adopting a contrib module, and for retiring a module or a Bluefly package, so that every decision lands as a record in module-ownership-matrix.md or bluefly-package-map.md with evidence. It executes rules that already have owners and does not restate them:
- the contrib-first law: contrib-first-policy.md;
- the ownership ladder and "evaluation is not installation" record: drupal-standard.md sections 12, 13;
- the scoring rubric (green / yellow / red): DRUPAL-CONTRIB-FIRST-EVALUATION-PLAYBOOK.md;
- upstream discovery and contribute-before-fork: how-we-build-in-drupal.md sections 3, 25;
- the
PROVEN_GAPclassification: drupal-site-building-standard.md section 25.
Principle: the matrix is the memory. If a module has a record with a LAST_VERIFIED date newer than the facts you would gather, reuse the record. If not, produce one. Adoption and retirement are the same procedure run in opposite directions; both end with a record and evidence, and neither begins with composer require or drush pm:uninstall.
Part A — Adoption¶
A1. State the requirement, not the module¶
Write the capability in one sentence without naming a module. Check capability-ownership-map.md: if an owner exists, the task is to configure or extend that owner, and this playbook ends here.
A2. Check the matrix¶
Search the matrix for candidates. A candidate with DO_NOT_USE needs new facts to reopen; a candidate with ADOPT_WHEN_NEEDED needs the trigger stated (real content, real media, a consuming View) and proof the trigger fired; EVALUATE needs the open question answered.
A3. Verify upstream¶
Gather, with dates, the facts the record needs: current release, Drupal 11 compatibility, security coverage, maintenance state, dependencies (from packages.drupal.org requires), reported usage, and what the module integrates with. Apply the rubric. Any alpha, beta, dev or uncovered candidate must carry an explicit SECURITY_COVERAGE and MAINTENANCE_STATE answer and, if adopted, an explicit acceptance statement (as tool and ai_context carry).
Reject on sight: a Drupal AI extension that hard-requires one provider; a second module for a concern that already has an owner on the site (a second CKEditor AI plugin, a third SEO analyser, a fifth SVG module, a second cache stack); a module whose functionality has moved to core.
A4. Measure the site and the estate¶
- Site: installed or enabled state, existing configuration, consumers (Views using it, fields using it, links using it, pipelines, models).
OBSERVEDwith the command. - Bluefly overlap: which Bluefly package, script or planned module does the same thing. Name it. Name what the adoption lets Bluefly delete.
A5. Decide and record¶
Fill the record (format below). DISPOSITION ∈ ADOPT, ADOPT_WHEN_NEEDED, EVALUATE, DO_NOT_USE. NET_OWNERSHIP_EFFECT must be N or SN when the adoption replaces Bluefly code or scripts; 0 when it fills a real gap with no Bluefly overlap; P is a reason to stop and reconsider.
A6. Implement as configuration¶
Add with Composer in an isolated worktree; enable; configure; export configuration; move the reusable configuration to its recipe owner (DRUPAL-RECIPE-FACTORY-PLAYBOOK.md); commit through the normal MR flow. Extension gaps go upstream (issue, patch, MR) with a thin temporary adapter at most.
A7. Retire what it replaced¶
Adoption is not complete while the Bluefly code or script it replaces still exists. Run Part B on that surface in the same change set or the immediately following one, and link both records.
Part B — Retirement (module or Bluefly package)¶
B1. Prove zero or migratable consumers¶
For a contrib module: enabled state; config objects referencing it; Views, fields, links, pipelines, models, ECA actions and Tool plugins that depend on it; other modules that declare it as a dependency. For a Bluefly package: the same, plus other Bluefly repositories that depend on it, Packs and Formulas that select it, and any drupal.org release consumers. Every consumer is either NONE (OBSERVED, command cited) or has a named migration.
B2. Name the replacement owner¶
Core, contrib, configuration, recipe, another Bluefly package, or nothing (the capability had no requirement). "Nothing" must be stated as a finding, not assumed.
B3. Preserve unique work¶
Anything the package does that the replacement does not (a differentiation such as a governance extension, a protocol implementation, an SDC set) is either moved to its correct home or explicitly dropped with a reason. No unique work is lost by omission.
B4. Decide and record¶
Contrib module: DISPOSITION=DO_NOT_USE with the uninstall action. Bluefly package: KEEP, SHRINK, REPLACE, CONTRIBUTE_UPSTREAM or DELETE_AFTER_MIGRATION, with the surface measured (PHP LOC, plugins, submodules, config objects, fields).
B5. Execute in order¶
Migrate consumers → uninstall (drush pm:uninstall) → remove configuration the uninstall left behind → remove from Composer → export configuration → prove the site imports cleanly on a fresh environment → remove the repository or archive it with its consumers' migration linked.
B6. Acceptance evidence¶
The retirement is accepted when all of the following are OBSERVED and cited:
CONSUMERS_BEFORE=<n, command>
CONSUMERS_MIGRATED=<n, where>
MODULE_UNINSTALLED=YES|NO
COMPOSER_REMOVED=YES|NO
CONFIG_OBJECTS_REMOVED=<n>
FIELDS_REMOVED=<n>
CONFIG_IMPORT_FRESH_ENV=PASS|FAIL
CUSTOM_PHP_ADDED=0
NET_LOC_DELTA=-<removed> / +<added>
MATRIX_RECORD_UPDATED=YES
PACKAGE_MAP_UPDATED=YES|N/A
CUSTOM_PHP_ADDED other than 0 turns a retirement into an adoption of custom code and re-enters the ladder at the bottom.
The record¶
One record per module, appended to or updated in the matrix. Fields match module-ownership-matrix.md; the specification section 25 format adds the optional fields marked below.
MODULE=
DRUPAL_ORG_URL= (optional)
LAST_VERIFIED=YYYY-MM-DD
UPSTREAM_STATUS= release, maintenance state, reported usage
LATEST_VERIFIED_RELEASE= (optional; dated)
D11_COMPATIBILITY=Y|N|NOT_ESTABLISHED
SECURITY_COVERAGE=Y|N|NOT_ESTABLISHED
MAINTENANCE_STATE= (optional)
CAPABILITIES=
DEPENDENCIES= (optional)
INTEGRATES_WITH= (optional)
CURRENT_BLUEFLY_USE= site state at LAST_VERIFIED, OBSERVED
CURRENT_BLUEFLY_OWNER= who owns the capability today (a package, a script, a theme, none)
OVERLAPPING_BLUEFLY_PACKAGES=
DISPOSITION=ADOPT|ADOPT_WHEN_NEEDED|EVALUATE|DO_NOT_USE|REPLACE_BLUEFLY|KEEP_BLUEFLY|CONTRIBUTE_UPSTREAM
WHAT_IT_REPLACES=
NET_OWNERSHIP_EFFECT=SN|N|0|P
MIGRATION_NOTES= (optional)
EVIDENCE= ledger audit path and row, or the MR
A Bluefly package record uses the package-map fields (PACKAGE, PURPOSE, UPSTREAM_OWNER_IF_ANY, BLUEFLY_DIFFERENTIATION, CURRENT_DISPOSITION, OVERLAP_EVIDENCE).
Fields you cannot support with evidence are NOT_ESTABLISHED, never guessed. Version numbers are dated facts for the record, never for a standard.
Promotion¶
A single adoption or retirement is project evidence. When a run produces a reusable lesson (a module class that keeps appearing with no consumer, a provider-locked pattern, a duplication pattern across packages), promote it: dated summary to ledger/audits/, the record to the matrix, and — only if Bluefly accepts it as a rule — a proposed in-place edit to the owning standard. The pipeline is defined in Engineering-Standard/reference/drupal/README.md.