STD-DRUPAL-003: Bluefly Drupal-Native Architecture & Upstream-First Refactoring Contract¶
Status: Approved Governing Standard Authority: Thomas P. Scola Jr. — Drupal Architecture Direction Date: 2026-10-04 Bead: hq-zhg4 (coordination); see §0 for the active producer bead this does not override
Naming note: Engineering-Standard/standards/core/STD-DRUPAL-002-module-architecture-decision-matrix.md
also carries a STD-DRUPAL-002-shaped filename but does not self-declare that ID in its own
content (no Status/Authority header) and covers a different scope (contrib-module adoption
matrix, not custom-module decomposition). The real, self-declared STD-DRUPAL-002 is
ledger/STD-DRUPAL-002-composition-and-theme-doctrine.md. This contract takes 003. The
filename collision on the other file is a separate doc-hygiene defect, not resolved here.
0. EXECUTION PRECEDENCE — READ BEFORE APPLYING §41's PRIORITY ORDER¶
Active claimed producer work finishes before this contract's priority order starts.
ACTIVE CLAIMED PRODUCER → COMPLETE (CI → WITNESS → merge → consumer update → close bead)
→ THEN apply this contract's global priority order (§41)
Current example: bead mba-3lyul (mcp_registry/mcp_gateway convergence) is already
claimed and in progress. Do not abandon it to start ai_agents_orchestra or any other P0 item
below. Finish what is claimed first.
One producer at a time. Parallel read-only investigation/audit across multiple producers is fine. Parallel destructive refactoring across multiple producers at once is not — it multiplies blast radius and makes WITNESS verification ambiguous about which change caused which effect.
Purpose¶
This contract governs bluefly.io and every Bluefly-owned Drupal module, recipe, theme,
integration, and package consumed by or designed for the site.
The objective is not merely to make the current code work. The objective is:
USE DRUPAL CORRECTLY.
DELETE PARALLEL FRAMEWORKS.
USE CORE FIRST. USE CONTRIB SECOND. USE CONFIGURATION THIRD. USE RECIPES FOURTH.
USE EXISTING DRUPAL EXTENSION POINTS. CUSTOM PHP LAST.
MAKE BLUEFLY'S CUSTOM CODE SMALLER, MORE GENERIC, MORE UPSTREAMABLE, AND EASIER TO DELETE.
Every custom line of code is a liability until an upstream gap proves it is necessary.
1. Required Implementation Order¶
DRUPAL CORE → EXISTING CONTRIB → CONTRIB CONFIGURATION → RECIPE / CONFIG ACTION
→ CANVAS / SDC → ECA / FLOWDROP → DRUPAL AI → AI_AGENTS → TOOL API → MCP
→ EXISTING BLUEFLY MODULE → NEW CUSTOM CODE
Custom code may only survive when every earlier layer has been evaluated and rejected with evidence.
2. MCP Ownership Classification (corrected)¶
Drupal.org's current direction is drupal/mcp_server (MCP Server 2.x, built on Tool API,
HTTP/STDIO transport, OAuth-oriented config) as the actual server implementation target.
drupal/mcp is transitional/umbrella, not the target — do not route server ownership there.
MCP_SERVER = drupal/mcp_server (plugins, transport, OAuth config)
MCP_CLIENT = mcp_client (unchanged — client-side responsibility stays separate,
do not blindly swap this to mcp_server)
MCP_TOOL_BRIDGE = mcp_server's own plugins
MCP_RESOURCES = mcp_server's own plugins
MCP_PROMPTS = mcp_server's own plugins
MCP_OAUTH = mcp_server's own config
Everywhere elsewhere in this contract that says drupal/mcp / mcp_client for server
ownership, read it as corrected to drupal/mcp_server. Client-side mentions of mcp_client
are unchanged.
3. What Counts as a Violation¶
Every implementation must be classified against:
IS_THIS_ACTUALLY_DRUPAL?
DOES_CORE_ALREADY_DO_THIS? DOES_CONTRIB_ALREADY_DO_THIS?
DOES_DRUPAL_AI_ALREADY_OWN_THIS? DOES_AI_AGENTS_ALREADY_OWN_THIS?
DOES_TOOL_API_ALREADY_OWN_THIS? DOES_ECA_ALREADY_OWN_THIS?
DOES_FLOWDROP_ALREADY_OWN_THIS? DOES_MCP_SERVER_ALREADY_OWN_THIS?
DOES_ADVANCEDQUEUE_ALREADY_OWN_THIS? DOES_MIGRATE_ALREADY_OWN_THIS?
DOES_CANVAS_ALREADY_OWN_THIS? DOES_VIEWS_ALREADY_OWN_THIS?
DOES_CONFIGURATION_ALREADY_OWN_THIS? DOES_A_RECIPE_ALREADY_OWN_THIS?
IS_THIS_FUNCTION_IN_THE_WRONG_MODULE?
IS_THIS_GENERIC_ENOUGH_TO_BELONG_UPSTREAM?
IS_THIS_A_ONE_OFF_BLUEFLY_IMPLEMENTATION_OF_A_GENERIC_PROBLEM?
IS_THIS_AN_INTERNAL_DEV_TOOL_INSIDE_A_PRODUCTION_DRUPAL_MODULE?
IS_THIS_CUSTOM_BECAUSE_SOMEBODY_DID_NOT_LOOK_FOR_THE_EXISTING_FRAMEWORK?
If yes, it is remediation work.
4. P0 — ai_agents_orchestra Must Be Dismantled¶
Repository: blueflyio/agent-platform/drupal/private/ai_agents_orchestra
The module's own README says: "NEVER implement: custom vector stores, raw Guzzle clients,
custom LLM mechanics." Current source does all three — custom agent entities, agent-type
entities, orchestration sessions, AI provider plugins, provider management, workflow engines,
vector memory, Qdrant integration, dashboards, marketplaces, registries, MCP controllers,
execution controllers, policy engine, audit layer, agent routing, framework bridges, CrewAI/
LangChain/OpenAI abstractions, GitLab ML inference, multi-site management, ROI systems,
performance agents, queue orchestration, context/memory orchestration — plus direct
GuzzleHttp\Client/curl_init()/curl_exec() calls, a hardcoded
https://llm-platform.ddev.site, and fake embedding behavior (array_fill, crc32)
presented as vector logic. Not acceptable production architecture.
Decomposition targets:
| Capability | Move to |
|---|---|
| LLM/model execution | drupal/ai — no custom model client, OpenAI/Anthropic router, provider manager/SDK wrapper |
| Agents | drupal/ai_agents — AiAgent plugins, configurable entities, tools, instructions, permissions. Delete custom AIAgent entity, AIAgentType entity, agent-type plugin system, parallel registry/execution loop unless a gap is proven |
| Memory/context | drupal/ai_context, AI Search, Search API, configured vector provider — delete custom VectorMemoryService/QdrantVectorService/framework-specific memory wrappers |
| Event-driven workflow | ECA — no second generic PHP workflow engine |
| Visual/data workflow | FlowDrop — no second graph/workflow format |
| Async execution | AdvancedQueue / Queue API — no parallel scheduler |
| External automation bridges | drupal/orchestration — not a second internal workflow engine |
| Executable capabilities | drupal/tool — no arbitrary custom execution API |
| Discovery | DUADP / OSSA — not Orchestra's job |
| Policy | cedar_policy, contractplane_client, compliance-engine — no second PolicyEngine |
| Marketplace | ai_marketplace, Drupal entities, Views |
| Dashboards | Drupal Dashboard, Views, standard entity list builders — delete AgentDashboardController, EnhancedDashboardController, IntegratedDashboardController, UnifiedDashboardController, UnifiedAgentDashboardController and any other duplicate dashboard interpretation |
Config is also slop: parallel naming variants (ai-agent-orchestra.* / ai_agent_orchestra.*
/ ai_agents_orchestra.*, hyphenated/underscored/pluralized/singular), duplicate ECA models,
Views, ECK entity types/bundles, fields, Group types, roles, workflow configs, queue configs
under slightly different machine names. Before deleting anything establish
ACTIVE_CONFIG / INSTALL_CONFIG / CONSUMERS / UNIQUE_PURPOSE, then converge to one
machine name, one config contract, zero duplicate config families.
5. P0 — alternative_services Is the Wrong Layer¶
Repository: blueflyio/agent-platform/drupal/private/alternative_services
Doing OS/workstation management from inside Drupal: DDEV start/stop/restart/addon install-
remove-registry/snapshots/db export-import/logs, Xdebug management, container execution,
Composer execution, Drush execution, local process execution, Tailscale Funnel management,
SSL operations, service orchestration/routing, external-process management. Drupal should not
be the workstation's shell. Move DDEV operations to DDEV addons/commands, host tooling, Gas
City execution, agent tooling, CI — not Drupal runtime PHP. Remove production Drupal code that
shells out to ddev/composer/drush/tailscale/host processes unless a narrowly scoped,
proven administrative integration absolutely requires it.
Submodule collisions, each duplicating existing ownership:
alternative_mcp → drupal/mcp_server, mcp_tools, Tool API
alternative_router → drupal/orchestration, ECA, HTTP Client Manager (by operation)
alternative_dragonfly → dragonfly_client (no second Dragonfly client)
alternative_services_ddev/... → move out of Drupal entirely
alternative_knowledge_graph → evaluate independently; don't bury a full graph platform here
6. P0 — recipe_onboarding Reimplements Drupal Recipes¶
Repository: blueflyio/agent-platform/drupal/private/recipe_onboarding
Defines its own Recipe Config Entity, RecipeListBuilder, RecipeForm, RecipeDeleteForm,
RecipeApplyForm, recipe status/version/author/tags/application state — while Drupal core
already has a Recipe system, and the site already requires drupal/core-recipe-unpack,
drupal/recipe_installer_kit, drupal/recipe_ops.
Target: Recipes remain Drupal Recipe artifacts (recipe.yml, config actions, Composer
packages). Recipe application uses the core Recipe API / RecipeRunner. Additional UX uses or
contributes to Recipe Installer Kit, Recipe Ops, Project Browser. No parallel Recipe entity
system. If Bluefly needs recipe inventory/validation/audit/fleet status, those are thin
services over the actual Recipe artifacts — the artifact stays the authority.
7. P1 — mcp_registry / mcp_gateway Is Too Large¶
~120 PHP files, 29 service-path files, 39 plugin-path files, self-described as "MCP server
registry, lifecycle management, health monitoring, capability aggregation, AgentDash
orchestration." Already depends on drupal/mcp_server-family modules and drupal/tool,
drupal/eca, and the module itself says discovery should delegate to contrib — finish that
decomposition:
MCP protocol → drupal/mcp_server
Tool discovery → Tool API, mcp_tools, tool_explorer
Agent discovery → DUADP, OSSA, ai_agents
Health → health_check, monitoring
Automation → ECA, Orchestration
Legitimate remaining purpose, only if contrib MCP doesn't already own it: MCP server registration metadata, connection configuration, server lifecycle state, site-specific MCP administration. Everything else moves out.
NOTE §0: this module is the subject of the already-claimed, in-progress mba-3lyul bead.
Finish that work through CI → WITNESS → merge → consumer update → close before treating this
section as new work to start.
8. P1 — ai_agents_client Is Mostly Correct, Still Too Broad¶
Correctly uses drupal/ai, ai_agents, Tool API, ECA, AdvancedQueue, HTTP Client Manager,
MCP, Health Check — keep that direction. Review and narrow: ProtocolRegistry,
HTTPAdapter, MCPAdapter, DuoAdapter, DiscoveryService, custom capability discovery,
custom protocol registration.
MCP transport → drupal/mcp_server, mcp_tools (no second MCP transport framework)
HTTP API description/exec → http_client_manager, api_normalization (no generic custom adapters)
Agent/service discovery → DUADP, OSSA, ai_agents
GitLab Duo-specific behavior → split out as ai_agents_client_duo or similar; core client stays
vendor-neutral
9. P1 — ai_agents_communication Should Not Own the Whole A2A World¶
Contains A2A models/task model/messages/Agent Card model/entity/registry/client, JSON-RPC
methods, REST/controller surfaces, Tool plugins, ECA plugins, AiAgent plugin, MCP integration
— too many layers in one module. Split generic A2A protocol (a2a_protocol: Agent Card,
Message, Task, Task Status, JSON-RPC transport, client/server semantics) from the thin
ai_agents_communication integration (A2A ↔ ai_agents, A2A Tool plugins, A2A ECA
integration). Discovery does not belong in the communication protocol — use DUADP/OSSA. Fix
the duplicate http_client_manager:http_client_manager dependency declaration in .info.yml.
10. P1 — dragonfly_client Has a Good Foundation, Owns Too Much¶
Strong pattern to keep: HTTP Client Manager, OpenAPI/API description, Tool plugins,
ai_agents; README correctly states "HTTP client via http_client_manager, zero custom PHP
HTTP client." Move out: agent catalog (→ ai_agents plugin manager, OSSA), capability catalog
(→ Tool API plugin manager), discovery manifest/endpoints (→ DUADP/duadp_client/OSSA — this
module provides Dragonfly metadata to those systems, it does not implement discovery), agent
memory (→ ai_context), vector operations (→ Drupal AI Search/Search API/configured vector
provider), generic token/cost telemetry (→ ai_logging/ai_observability/monitoring unless
genuinely Dragonfly-specific), rate limiting (→ HTTP Client Manager client policy, or generic
Drupal flood service — no invented rate-limit framework).
11. P1 — layout_system_converter Is a Migration Tool, Not a Platform¶
Contains 28 Tool plugins, AI Agent, FlowDrop nodes, migration jobs, component generation,
vector DB registration/health/presets/metrics, WebSocket dependencies — actual purpose is
legacy layouts → SDC/Canvas. Keep focused: layout discovery/conversion → Drupal Migrate,
Canvas APIs, SDC; AI reasoning → drupal/ai, ai_agents; workflow → FlowDrop/ECA; queue →
Drupal Queue/AdvancedQueue. Remove all generic vector-database administration (register/
unregister/health/metrics/presets/config) — unrelated to layout conversion, use the existing
AI/vector ecosystem. Fix source metadata: composer.json autoload declares
Drupal\canvas_converter\ while current source classes use Drupal\layout_system_converter\
— converge, no alias hacks, no dual namespaces.
12. P1 — external_migration Is Promising but Overbuilt¶
Already correctly uses Drupal Migrate, Migrate Plus, Migrate Tools, drupal/ai, ai_agents,
Tool API, ECA, FlowDrop, Canvas — keep those. Has accumulated a second application platform
around them: MigrationGraph, custom orchestrator, custom learning system, performance
optimizer, custom enhancement layer, custom pipeline states, custom publish/fidelity gates,
custom migration REST API, custom caching/rate limiting — each must be justified
independently. Drupal Migrate remains the migration authority (source/process/destination
plugins, migration config, messages, rollback, Migrate API/Plus/Tools) — do not recreate
Migrate semantics in MigrationGraph unless it only carries Canvas-specific intermediate
structure Migrate can't represent. AI field mapping via drupal/ai: keep. AI task
decomposition, if agentic: ai_agents, not a custom execution loop. Workflow: ECA/FlowDrop.
Publication governance: evaluate Content Moderation/Workflows/ECA before a custom publishing
state machine.
13. bluefly.io Consumer Repo — Non-Drupal Control-Plane Slop¶
Repository: blueflyio/assets/bluefly.io
.agents-workspace/*.mjs, ai.json control_primitives, Node-based governance validators —
not Drupal application functionality, useful but wrong ownership layer.
Move generic validation out of the site: workspace-status.mjs,
validate-runtime-consumer-install.mjs, validate-metadata-alignment.mjs,
validate-control-primitives-schema.mjs, run-validations.mjs → gitlab_components,
agent-buildkit, or another canonical shared Bluefly developer tool. One implementation, many
consumers.
validate-config-parity is more defensible (delegates to drush config:status rather than
reimplementing Drupal config comparison) but is still generic protection that belongs as a
shared Drush command / CI component / preflight package, not custom Node wrapper code
per-consumer.
validate-blu-chat-config.mjs checks whether YAML files exist and whether a role YAML
contains a permission string — this should be a Drupal Kernel/Functional test inside the
owning integration module, because Drupal can actually ask "does the role/permission/
assistant config entity/block plugin/AiAgent plugin resolve" — a JS string search cannot. Move
it.
ai.json must not become a second Drupal configuration system. It currently drifts from
reality (e.g. still describes layout_system = experience_builder while the project uses
current Canvas naming; lists custom modules status=active without proof against
core.extension.yml). ai.json != RUNTIME AUTHORITY — runtime truth comes from Composer,
Drupal extension discovery, active config, core.extension, runtime readback. Reduce
ai.json to agent/project metadata or generate its derived sections; do not hand-maintain a
second inventory of Drupal.
14. Duplicate Design Systems Must Go¶
.agents/DesignSystem/ and .agents/DesignSystem-new/ (JSX, HTML prototypes, CSS, tokens,
fonts, UI kits, duplicated logo assets, screenshots) may exist as historical design
references. They must not remain competing design authorities. Canonical production
authority: studio-ui (shared primitives) → bluefly_theme (Drupal SDC implementation) →
Canvas (page composition). One design token authority, one component authority, one
production theme. Historical prototypes: archive or reference only, never developed further
as another frontend.
15. Theme Build Tooling Belongs in the Theme Repo¶
Consumer package.json currently includes bootstrap, gulp, gulp-cli, gulp-sass,
sass — but the consumer declares itself a Drupal runtime consumer, and theme source belongs
to bluefly_theme. Theme build tooling moves to bluefly_theme/studio-ui. The site may
retain Playwright, Canvas CLI, consumer verification tooling where actually needed.
semantic-release / @semantic-release/* do not belong in this consumer either — release
architecture is centralizing in blueflyio/gitlab_components; remove repo-local competing
release automation. The project consumes the shared release pipeline, it does not invent its
own.
16. Stop Tracking Package Source Under web/¶
web/ is a Composer/runtime projection. ai.json still allowlists
web/themes/custom/bluefly_theme/ as temporarily tracked. Now that bluefly_theme has an
owning repository and Composer package, this exception needs a deletion plan. Target:
TRACKED_PACKAGE_SOURCE_UNDER_WEB=0. Theme development belongs in the theme repository; the
consumer installs the released package.
17. Canvas Should Replace Legacy Layout Authority¶
canvas, layout_builder, layout_builder_restrictions are all active — not automatically
wrong during migration, but target architecture is Canvas. Prove
LAYOUT_BUILDER_ACTIVE_CONTENT / LAYOUT_BUILDER_REQUIRED_MODULES /
CANVAS_MIGRATION_COMPLETE. When LAYOUT_BUILDER_ACTIVE_CONTENT=0, retire the legacy layout
stack. Do not keep two layout systems forever.
18. Contrib Overlap — Pick One Owner Per Concern¶
Analytics: drupal_cms_google_analytics, ga4_google_analytics, google_analytics,
google_tag, gtm, matomo all present. Define PRIMARY_ANALYTICS / TAG_MANAGER /
PRIVACY_ANALYTICS, remove unused alternatives.
CAPTCHA/bot protection: captcha, recaptcha, friendlycaptcha, antibot, honeypot.
Define FORM_SPAM_BASELINE (honeypot|antibot) and INTERACTIVE_CHALLENGE
(friendlycaptcha|recaptcha), remove unused providers.
Email: mailsystem, smtp, sendgrid_integration, symfony_mailer_lite, easy_email,
easy_email_theme. Separate mail transport / email template-entity / mail routing, one owner
each. Likely clean pattern: Symfony Mailer transport + one provider transport + Easy Email
only if its content/template model is actually required — prove current requirements first.
Search: core Search, search_node, Search API, Search API DB, Search API Solr,
search_api_exclude. Search API is the canonical abstraction; define
PRODUCTION_BACKEND/LOCAL_BACKEND. If Solr is production, Search API Solr owns production
indexing; Search API DB may remain only as intentional local/fallback. Core Search/Search Node
should not remain a parallel user-facing system without documented use.
Workflow/orchestration: Content Moderation/Workflows = editorial publication state; ECA =
event-driven automation; FlowDrop = visual/data pipeline composition; AdvancedQueue = durable
async work; Orchestration = external automation bridge; Scheduler = scheduled publish/
unpublish; Ultimate Cron = cron execution control. Maestro and State Machine must prove
unique requirements not already covered above. No custom Bluefly module re-implements any of
these layers.
19. AI Provider Law¶
Multiple providers (OpenAI, Anthropic, Hugging Face, Gemini, LiteLLM, Apple) can legitimately
coexist, but all model calls go through drupal/ai. No module picks an SDK directly — no
Guzzle request to OpenAI, no curl to Anthropic, no custom LLM gateway, no custom provider
abstraction. If LiteLLM is the selected production router, configure Drupal AI's LiteLLM
provider — do not bypass Drupal AI to reach it. Provider choice is configuration, not
application architecture.
Hardcoded model IDs are forbidden (e.g. gpt-3.5-turbo, openai literals inside
orchestration logic) — use Drupal AI's default provider / configured operation type / model,
unless a feature explicitly requires a particular model and documents why.
20. Agent, Tool, and API Law¶
Agent law: if a capability is an AI agent, the AiAgent plugin is the implementation
boundary. No custom agent runner, ReAct loop, agent state machine, provider-agent abstraction,
or "framework bridge" execution engine before proving ai_agents cannot do it.
Tool law: executable capabilities exposed to AI should be Tool plugins. No custom
/api/.../execute controller merely to call a PHP service — prefer Tool API → MCP exposure
where needed → ai_agents consumption → ECA/FlowDrop integration. One capability, multiple
adapters.
API law: before any custom controller route, evaluate JSON:API, Drupal REST, Tool API,
drupal/mcp_server, HTTP Client Manager, api_normalization, OpenAPI. Standing site doctrine
already says "JSON:API / Tool API / existing packages before bespoke routing" — enforce it.
21. Single-Purpose Functionality Should Move Upstream¶
Anything not Bluefly-specific, not product-specific, not business-specific, and reusable across Drupal sites must be evaluated for Drupal contrib, an existing upstream project, a generic Bluefly contrib-ready module, a shared GitLab Component, or a shared CLI/tool. Examples from the current estate: config export safety, layout conversion, recipe management UX, MCP registry, A2A protocol, external migration, generic fleet client. These are generic problems — they should not remain permanently private Bluefly glue if genuinely valuable.
22. Function Ownership Map¶
| Functionality | Current/likely wrong home | Correct owner |
|---|---|---|
| Model/provider abstraction | Orchestra/custom clients | drupal/ai |
| Agent execution | Orchestra/custom loops | ai_agents |
| Agent definition portability | custom agent registries | OSSA |
| Agent discovery | client/communication/Dragonfly registries | DUADP |
| AI context/memory | Orchestra/Dragonfly custom memory | ai_context |
| Vector search | custom Qdrant/vector managers | AI Search/Search API/provider |
| Executable AI capability | custom controller/service | Tool API |
| MCP server | custom adapters/gateway, drupal/mcp |
drupal/mcp_server |
| MCP tool discovery | custom registry | mcp_tools, Tool API |
| Internal automation | custom workflow engine | ECA |
| Visual pipeline | custom graphs | FlowDrop |
| Async work | custom task executor | AdvancedQueue/Queue API |
| Editorial state | custom publish state | Content Moderation/Workflows |
| External workflow bridge | custom orchestration client | drupal/orchestration |
| Recipe execution | custom Recipe entity | Drupal Recipe API |
| Migration | custom migration engine | Migrate API |
| Site composition | custom page builder | Canvas |
| Component rendering | custom HTML controllers | SDC |
| Lists/dashboards | custom list controllers | Views/Dashboard |
| API clients | raw Guzzle wrappers | HTTP Client Manager/api_normalization |
| Credentials | env strings/custom secrets | Key |
| Health | custom health framework | Health Check/Monitoring |
| DDEV operation | Drupal module | DDEV/host tooling/Gas City |
| CI/release policy | consumer scripts | gitlab_components |
| Generic repository validation | site Node scripts | shared build/tooling project |
23. Custom Code Survival Test¶
Every Bluefly custom module must produce:
MODULE= CUSTOM_PURPOSE=
CORE_ALTERNATIVE= CONTRIB_ALTERNATIVE=
DRUPAL_AI_ALTERNATIVE= AI_AGENTS_ALTERNATIVE=
TOOL_ALTERNATIVE= ECA_ALTERNATIVE=
FLOWDROP_ALTERNATIVE= MCP_ALTERNATIVE=
WHY_NONE_SUFFICE=
MINIMUM_CUSTOM_SURFACE=
UPSTREAM_CANDIDATE=YES|NO
DELETION_TRIGGER= OWNER=
If WHY_NONE_SUFFICE cannot be filled with a concrete technical gap: delete/replace the
custom code.
24. Module Size Is a Warning Signal¶
A module whose description requires words like "framework," "platform," "orchestration system," "registry," "marketplace," "memory," "dashboard," "workflow engine," "provider manager," "service mesh" — all in one project — is almost certainly too broad. One Drupal module, one coherent capability boundary. Submodules may provide optional adapters. No mini-platforms inside Drupal.
Prohibited pattern: "Drupal already has X → build another X" for plugin managers, entities/registries, queue, workflow, AI providers, agents, tools, MCP, Migrate. That is architecture slop.
25. Do Not Mass-Delete¶
This is a refactoring/deletion contract, not permission to blindly remove source. Before any
deletion establish CURRENT_CONSUMERS / CONFIG / ACTIVE_RUNTIME / UNIQUE_FUNCTION /
TEST_COVERAGE / UPSTREAM_REPLACEMENT / MIGRATION_PATH. Then replace → verify → remove.
Never delete → hope.
26. Required Audit of Every Bluefly Drupal Module¶
MODULE= LINES_OF_CODE= SERVICES= CONTROLLERS= ENTITIES= PLUGIN_MANAGERS=
AI_AGENTS= TOOLS= ECA_PLUGINS= FLOWDROP_NODES= QUEUES= REST_ROUTES=
CUSTOM_HTTP_CLIENTS= CUSTOM_PROCESS_EXECUTION= CUSTOM_STORAGE= CUSTOM_WORKFLOW=
CUSTOM_DISCOVERY= CUSTOM_POLICY= CUSTOM_VECTOR= CUSTOM_AGENT_RUNTIME=
Classify every function: KEEP / MOVE / REPLACE_WITH_CORE / REPLACE_WITH_CONTRIB /
REPLACE_WITH_CONFIG / REPLACE_WITH_RECIPE / REPLACE_WITH_ECA / REPLACE_WITH_FLOWDROP /
REPLACE_WITH_DRUPAL_AI / REPLACE_WITH_AI_AGENTS / REPLACE_WITH_TOOL / REPLACE_WITH_MCP_SERVER
/ UPSTREAM / DELETE.
27. Priority Order¶
Subordinate to §0 (active claimed producer finishes first; one producer at a time).
P0 ai_agents_orchestra
P0 alternative_services
P0 recipe_onboarding
P1 mcp_registry (NOTE: active claimed bead mba-3lyul — finish that, don't restart it)
P1 ai_agents_client
P1 ai_agents_communication
P1 dragonfly_client
P1 layout_system_converter
P1 external_migration
P1 bluefly.io .agents-workspace duplication
P1 ai.json runtime-authority drift
P1 tracked package source under web/
P2 analytics overlap
P2 CAPTCHA overlap
P2 mail overlap
P2 search overlap
P2 workflow/module overlap
P2 design-reference cleanup
Do not start with cosmetic cleanup while the parallel frameworks survive.
28. Target State¶
CUSTOM_LLM_CLIENTS=0 CUSTOM_MODEL_ROUTING=0
CUSTOM_AGENT_FRAMEWORKS=0 CUSTOM_GENERIC_WORKFLOW_ENGINES=0
CUSTOM_MCP_TRANSPORTS=0 CUSTOM_RECIPE_ENTITY_SYSTEMS=0
CUSTOM_DDEV_PROCESS_EXECUTION_IN_DRUPAL=0
CUSTOM_GENERIC_VECTOR_MANAGERS=0 HARDCODED_PROVIDER_MODELS=0
RAW_CURL_FOR_AI=0
DUPLICATE_DESIGN_SYSTEM_AUTHORITIES=0
DUPLICATE_RELEASE_SYSTEMS=0
UNJUSTIFIED_CUSTOM_MODULES=0 UNJUSTIFIED_CUSTOM_CONTROLLERS=0
UNJUSTIFIED_CUSTOM_SERVICES=0 UNJUSTIFIED_CONTRIB_OVERLAP=0
DRUPAL_AI_USED_FOR_AI=YES AI_AGENTS_USED_FOR_AGENTS=YES
TOOL_API_USED_FOR_CAPABILITIES=YES MCP_SERVER_USED_FOR_MCP=YES
ECA_USED_FOR_EVENT_AUTOMATION=YES FLOWDROP_USED_FOR_VISUAL_PIPELINES=YES
MIGRATE_USED_FOR_MIGRATION=YES CANVAS_USED_FOR_COMPOSITION=YES
SDC_USED_FOR_COMPONENTS=YES VIEWS_USED_FOR_LISTS=YES
RECIPES_USED_FOR_SITE_ASSEMBLY=YES
29. Required Receipt (per module)¶
MODULE= BEAD= CURRENT_PURPOSE=
AUDIT:
CORE_DUPLICATION= CONTRIB_DUPLICATION= BLUEFLY_DUPLICATION=
WRONG_LAYER= NON_DRUPAL_FUNCTIONS= DIRECT_HTTP=
DIRECT_LLM= CUSTOM_AGENT_RUNTIME= CUSTOM_WORKFLOW=
CUSTOM_VECTOR= CUSTOM_PROCESS_EXECUTION=
DISPOSITION:
KEEP= MOVE= CONTRIB_REPLACEMENT= CORE_REPLACEMENT= CONFIG_REPLACEMENT=
RECIPE_REPLACEMENT= ECA_REPLACEMENT= FLOWDROP_REPLACEMENT=
DRUPAL_AI_REPLACEMENT= AI_AGENTS_REPLACEMENT= TOOL_REPLACEMENT=
MCP_SERVER_REPLACEMENT= DELETE=
OWNERSHIP:
SOURCE_MODULE= TARGET_MODULES= UPSTREAM_CANDIDATE=
DELIVERY:
TESTS= CONFIG_MIGRATED= CONSUMERS_MIGRATED= OLD_CODE_REMOVED=
MR= CI= WITNESS= MERGED_TO_RELEASE=
CUSTOM_CODE_BEFORE= CUSTOM_CODE_AFTER= CUSTOM_CODE_REDUCTION_PERCENT=
UNEXPLAINED_DUPLICATION=0 UNACCOUNTED_FUNCTIONALITY=0
Final Contract¶
BLUEFLY DOES NOT BUILD A SECOND DRUPAL INSIDE DRUPAL.
CORE OWNS CORE THINGS. CONTRIB OWNS GENERIC DRUPAL CAPABILITIES.
DRUPAL AI OWNS MODEL ABSTRACTION. AI_AGENTS OWNS AGENT EXECUTION.
AI_CONTEXT OWNS AI CONTEXT. TOOL API OWNS EXECUTABLE CAPABILITIES.
DRUPAL/MCP_SERVER OWNS MCP. ECA OWNS EVENT AUTOMATION.
FLOWDROP OWNS VISUAL PIPELINES. ADVANCEDQUEUE OWNS DURABLE ASYNC WORK.
MIGRATE OWNS MIGRATION. CANVAS OWNS PAGE COMPOSITION.
SDC OWNS COMPONENTS. VIEWS OWNS LISTING AND QUERY PRESENTATION.
RECIPES OWN SITE ASSEMBLY. KEY OWNS CREDENTIAL REFERENCES.
HTTP CLIENT MANAGER / API NORMALIZATION OWN API CLIENT ABSTRACTION.
GAS CITY OWNS ENGINEERING ORCHESTRATION. DRUPAL DOES NOT MANAGE DDEV, TMUX, TAILSCALE, OR THE
FACTORY.
CUSTOM CODE EXISTS ONLY AFTER A PROVEN GAP. GENERIC CUSTOM CODE GETS UPSTREAMED.
BLUEFLY-SPECIFIC CODE STAYS THIN. DUPLICATED FRAMEWORKS GET DELETED.
HACKS GET REDEVELOPED. STUBS DO NOT SHIP.
FAKE IMPLEMENTATIONS DO NOT SHIP. EVERY FUNCTION HAS ONE OWNER.
EVERY MODULE HAS ONE COHERENT PURPOSE. EVERY CUSTOM LINE HAS A REASON TO EXIST.
IF DRUPAL ALREADY DOES IT, WE USE DRUPAL.
ACTIVE CLAIMED PRODUCER WORK FINISHES BEFORE THIS CONTRACT'S PRIORITY ORDER STARTS.
ONE PRODUCER AT A TIME FOR DESTRUCTIVE REFACTORING.
First three to attack, after any active claimed producer work is finished: ai_agents_orchestra,
alternative_services, recipe_onboarding. They have the clearest violations and the largest
opportunity to delete custom code rather than refactor it. ai_agents_orchestra in particular
is a decomposition project, not a cleanup: preserve the few unique integrations, move them onto
the existing Drupal AI/AI Agents/Tool/ECA/FlowDrop stack, then retire the parallel framework.