How We Build in Drupal: The Upstream-First Operating Manual¶
1. The Drupal Upstream Supply Chain¶
Bluefly does not treat Packagist as the canonical distribution channel for Drupal contrib. Drupal contrib packages are resolved through Drupal.org's Composer repository:
https://packages.drupal.org/8
Drupal.org documentation explicitly notes that most contributed Drupal modules are not published on Packagist and should instead be resolved through the Drupal Composer repository.
graph LR
C["Drupal Composer Repository<br/>packages.drupal.org/8"] --> |"Modules, Themes, Recipes"| D["Drupal Runtime<br/>ContextControl.ai / Drupal Sites"]
G["DrupalCode GitLab<br/>git.drupalcode.org/project/*"] --> |"Canonical Upstream Source"| D
N["NPM<br/>@drupal-canvas/* and Drupal JS packages"] --> |"Canvas / Frontend Tooling"| D
| Channel | Canonical Location | Role | Key Artifacts |
|---|---|---|---|
| Drupal Composer Repository | https://packages.drupal.org/8 |
Primary Drupal PHP dependency distribution | drupal/* modules, themes, recipes, Drupal core packages |
| DrupalCode | https://git.drupalcode.org/project/<project> |
Canonical upstream source and development history | source, branches, merge requests, CI, release tags |
| Drupal.org Project Pages | https://www.drupal.org/project/<project> |
Release, security, compatibility, ecosystem and documentation discovery | supported versions, security coverage, ecosystem links, issue queues |
| NPM | npmjs.com |
JS/frontend/developer tooling | @drupal-canvas/cli, @drupal-canvas/create, @drupal-canvas/eslint-config, Drupal JS packages |
Composer Rule¶
Drupal dependencies must be installed from the site's Composer authority.
Normal form:
composer require drupal/<project>
Examples:
composer require drupal/ai_context
composer require drupal/tool
composer require drupal/tool_belt
composer require drupal/eca
composer require drupal/ai_integration_eca
composer require drupal/api_orchestrator
composer require drupal/key
composer require drupal/canvas
Drupal.org recommends Composer for contributed modules and themes and uses the drupal/<machine_name> namespace.
Do not manually download contrib archives into the codebase.
Do not edit:
web/modules/contrib/*
web/themes/contrib/*
vendor/*
2. Upstream-First Is a Decision Tree¶
Before Bluefly writes Drupal code:
CORE
↓
STABLE CONTRIB
↓
CONTRIB CONFIGURATION
↓
DRUPAL CONFIGURATION
↓
RECIPE
↓
CONFIG ACTION
↓
CANVAS / SDC
↓
ECA / MODELER / FLOWDROP
↓
DRUPAL AI
↓
AI AGENTS
↓
TOOL API
↓
TOOL BELT
↓
MCP / EXISTING INTEGRATION
↓
EXISTING BLUEFLY MODULE
↓
CUSTOM PHP ONLY AFTER PROVEN GAP
Permanent rule:
Reuse the owner. Fix the owner. Extend the owner. Do not duplicate the owner.
Before introducing custom code:
UPSTREAM_PROJECT_SEARCHED=YES
ECOSYSTEM_SEARCHED=YES
EXISTING_CONTRIB_OWNER_SEARCHED=YES
EXISTING_BLUEFLY_OWNER_SEARCHED=YES
CAPABILITY_GAP_PROVEN=YES
CUSTOM_CODE=NO
3. Mandatory Upstream Discovery Protocol¶
Before changing architecture or adding a dependency:
3.1 Find the project¶
https://www.drupal.org/project/<machine_name>
Check:
CURRENT_RELEASE=
SECURITY_COVERAGE=
SUPPORTED_DRUPAL_VERSIONS=
MAINTENANCE_STATUS=
RECOMMENDED_BRANCH=
3.2 Check the ecosystem¶
https://www.drupal.org/project/<machine_name>/ecosystem
Look for: - companion modules; - bridges; - submodules; - replacement projects; - integrations; - maintained successors.
Do not create an integration until the ecosystem has been checked.
3.3 Inspect canonical source¶
https://git.drupalcode.org/project/<machine_name>
Use upstream source to determine: - services; - plugin types; - extension points; - config entities; - events; - hooks; - APIs; - tests; - actual implementation behavior.
3.4 Check the installed version¶
Source documentation must match the version actually installed.
Use:
composer show drupal/<project>
composer.lock
Do not implement against HEAD, 1.x-dev, old documentation, blog posts, or issue comments when the running site is pinned to another release.
4. Drupal Recipes Are the Default Site-Assembly Mechanism¶
Drupal Recipes are applied configuration packages.
Drupal's documentation recommends installing recipes with Composer, then applying them using Drush 13+ or Drupal core's recipe command.
Example:
composer require drupal/<recipe>
drush recipe ../recipes/<recipe>
# or:
php core/scripts/drupal recipe ../recipes/<recipe> -v
A Recipe is not a custom module. The Recipe itself should remain declarative.
Typical package:
recipe_contextual_memory/
├── composer.json
├── recipe.yml
└── config/
The Composer package may declare:
{ "type": "drupal-recipe" }
But business logic does not belong in PHP classes inside a Recipe.
Use:
INSTALL MODULES
IMPORT OTHER RECIPES
CREATE CONFIG
APPLY CONFIG ACTIONS
GRANT PERMISSIONS
UPDATE CONFIGURATION
5. Recipe Composition Law¶
Bluefly Recipes should be small and composable.
Correct:
administrator_role
↓
contextual_memory_base
↓
contextual_memory_ai
↓
contextcontrol_site
Incorrect:
ONE GIANT RECIPE WITH EVERY POSSIBLE FEATURE
A higher-level Recipe should compose lower-level capabilities.
Example:
name: Contextual Memory
description: Configures governed AI context and contextual memory.
type: Bluefly
recipes:
- core/recipes/administrator_role
install:
- ai
- ai_context
- tool
- tool_belt
config:
actions:
user.role.administrator:
grantPermissions:
- 'administer ai context'
Rules:
RECIPE_OWNS_SITE_ASSEMBLY=YES
RECIPE_OWNS_RUNTIME_BUSINESS_LOGIC=NO
RECIPE_OWNS_GENERIC_INFRASTRUCTURE=NO
6. Configuration Before Code¶
Do not write PHP merely to establish site configuration.
Preferred sequence:
EXISTING CONFIGURATION
↓
CONFIG ENTITY
↓
RECIPE CONFIG
↓
CONFIG ACTION
↓
ECA / TOOL CONFIGURATION
↓
CUSTOM CODE
After configuring a site:
drush config:export -y
- Do not blindly hand-author large config trees.
- Do not blindly export the entire site into a Recipe.
- Curate only what the Recipe owns.
7. Drush Is an Execution Interface, Not an Architecture¶
Use Drush for deterministic site operations.
Examples:
drush status
drush pm:list
drush config:status
drush config:export -y
drush cache:rebuild
drush recipe ../recipes/<recipe>
For code generation, Drush provides generators including:
drush generate module
drush generate drush:command
But:
GENERATE MODULE does not mean A MODULE SHOULD EXIST
Generate custom code only after upstream discovery proves the capability gap. Do not invent undocumented Drush commands and treat them as standard Drupal workflow.
8. Tool API Before Custom Service APIs¶
Drupal Tool API is the standard typed executable-unit abstraction.
Tool API defines reusable executable logic with typed inputs and outputs and is intended for reuse by systems including AI agents and workflow engines.
Use Tool API when a Drupal capability should be callable by:
AI AGENT
ECA
MCP
DRUSH ADAPTER
OTHER AUTOMATION
Prefer:
DOMAIN CAPABILITY → TOOL API PLUGIN
DOMAIN CAPABILITY → CUSTOM CONTROLLER → CUSTOM REST ROUTE → CUSTOM AUTH → CUSTOM CLIENT
9. Tool Belt Before Reimplementing Drupal Operations¶
Tool Belt is the contrib collection of reusable Tool API plugins for common Drupal operations.
Current upstream provides tools across: - content; - entity definitions; - fields; - users; - system operations; - translation; - workspaces.
Tool Belt exists specifically so Drupal installations do not repeatedly recreate common Drupal operations.
Therefore:
NEED ENTITY CRUD → CHECK TOOL BELT
NEED FIELD/BUNDLE MANAGEMENT → CHECK TOOL BELT
NEED WORKSPACE OPERATIONS → CHECK TOOL BELT
NEED USER OPERATIONS → CHECK TOOL BELT
Do not freeze tool counts such as "43 tools" into architecture doctrine. Tool inventories change. Treat upstream capability as dynamic.
10. Context Control Center Is the Drupal Context Authority¶
drupal/ai_context is upstream Drupal's Context Control Center.
Current upstream provides governed context items with capabilities including revisions, drafts, moderation, scheduling, scopes, and agent-context integration.
Bluefly architecture:
AI CONTEXT / CONTEXT CONTROL CENTER = Drupal-native governed context model
ContextControl.ai = product experience + governance around that upstream model
Do not duplicate: - context entity model - revision system - moderation - scope system - context selection
inside custom Bluefly modules when upstream owns them.
Bluefly extensions should focus on capabilities upstream does not own, such as:
PROVENANCE
TENANCY
CONFLICT CONTROL
EXTERNAL AGENT CORRELATION
POLICY ENFORCEMENT
GOVERNED MEMORY SEMANTICS
11. Memory Architecture¶
The memory stack is:
HUMAN GOVERNED CONTEXT
↓
drupal/ai_context
↓
Bluefly governance extensions
↓
Drupal AI / AI Search / vector-provider layer
↓
vector storage
kb_cache must not recreate a generic vector client already owned by Drupal AI/vector provider infrastructure.
Bluefly-specific ownership is limited to:
PROVENANCE
GAID / IDENTITY SEMANTICS
TENANCY
IDEMPOTENCY
CONFLICT DETECTION
WRITE GUARDS
CEDAR AUTHORIZATION
MEMORY GOVERNANCE
12. ECA Is the Event / Workflow Layer¶
Prefer ECA when behavior can be expressed as:
EVENT → CONDITION → ACTION
Use ECA before building: - custom event subscribers - custom orchestration services - custom queue glue - custom callback pipelines
when the workflow is configuration-expressible.
For AI workflows, use:
drupal/ai_integration_eca
not obsolete predecessor integrations.
Architecture:
Drupal Event → ECA → Tool / AI / API action → governed result
13. External APIs: Use API Orchestrator Before Custom HTTP Clients¶
For ordinary external REST/HTTP integrations:
CHECK drupal/api_orchestrator FIRST
API Orchestrator provides upstream configurable: - services; - endpoints; - authentication headers; - retries; - queues; - logging; - event dispatch; - ECA integration.
Therefore:
EXTERNAL HTTP API → API ORCHESTRATOR CONFIG → ECA / TOOL API
CUSTOM GUZZLE SERVICE
CUSTOM CURL
CUSTOM RETRY LOOP
CUSTOM REQUEST LOG
14. Webhooks: Use Existing Webhook Owners¶
Drupal's Webhooks contrib module supports both dispatching and receiving webhook calls and can expose received events to systems such as ECA.
Before creating a Bluefly webhook receiver:
CHECK PLATFORM NATIVE WEBHOOK
CHECK drupal/webhooks
CHECK API ORCHESTRATOR
CHECK EXISTING BLUEFLY INGRESS OWNER
15. Secrets: Key + External Secret Authority¶
Drupal Key is the Drupal abstraction for consuming sensitive values. Its own guidance ranks external secret management as the strongest storage approach.
Bluefly model:
1PASSWORD = secret authority
Drupal Key = Drupal-facing secret reference/consumption abstraction
Correct:
Drupal → Key → external provider/reference → secret consumed at runtime
Incorrect:
secret in config sync
secret in recipe.yml
secret in settings committed to Git
secret in Bead
secret in prompt
Permanent rule:
SECRET_VALUE_IN_SOURCE=NO
SECRET_VALUE_IN_CONFIG_EXPORT=NO
SECRET_VALUE_IN_RECIPE=NO
SECRET_VALUE_IN_TRANSCRIPT=NO
16. Canvas Is the Experience Composition Layer¶
Current stable Drupal Canvas is 1.11.x, not 1.9.x. Drupal.org lists Canvas 1.11.0 as a stable release for Drupal ^11.3.
Do not hardcode historical versions into architecture doctrine unless intentionally pinned by the product.
Architecture:
DRUPAL CONTENT / CONFIG / CONTEXT
↓
CANVAS
↓
CODE COMPONENTS / SDC / PAGES / TEMPLATES / REGIONS
↓
EXPERIENCE
Use Canvas for: - page composition; - Code Components; - global regions; - content templates; - shared frontend assets; - component-driven authoring.
Do not recreate a separate page-builder framework.
17. Canvas CLI Is the Local Code-Component Interface¶
Canonical package:
@drupal-canvas/cli
The local Canvas workflow uses:
npx canvas pull
npx canvas validate
npx canvas push
The CLI can manage: - Code Components; - JavaScript/TypeScript; - component metadata; - shared local code; - global CSS; - packages; - pages; - content templates; - global regions; - Brand Kit assets.
For new Canvas codebases:
npx @drupal-canvas/create@latest
Normal cycle:
npx canvas pull
npx canvas validate
npx canvas push --yes
Current push behavior includes pages, content templates and regions by default unless disabled by configuration or --no-* flags.
Do not maintain obsolete --include-pages or --include-regions assumptions when current CLI behavior has changed.
18. Canvas Agent Context¶
Canvas includes agent-context commands for retrieving authoritative metadata needed by coding agents.
Example:
npx canvas agents-context cer-expressions
npx canvas agents-context cer-preview <component>
These commands provide authoritative Canvas entity-reference metadata rather than requiring an agent to infer component shapes.
Rule:
CANVAS_METADATA_AVAILABLE → QUERY CANVAS → DO NOT GUESS
19. Canvas Validation Is Mandatory Before Push¶
Run:
npx canvas validate
npx canvas push
Canvas uses its own ESLint rules and component validation infrastructure. The official @drupal-canvas/eslint-config is automatically used by the CLI for component validation/build/push paths.
Do not create a competing Canvas validator.
20. Canvas Source Control Law¶
The local Canvas codebase belongs in Git.
Typical governed assets:
src/components/
canvas.config.json
canvas.brand-kit.json
package.json
package-lock.json
pages/
content templates
shared utilities
Secrets belong in environment/runtime secret authority.
Do not commit: - OAuth token files - client secrets - local credentials
Canvas documentation distinguishes repository configuration from .env/credential material.
21. Bluefly Drupal Architecture Overview¶
┌───────────────────────────────────────────────────────────────────────────┐
│ EXPERIENCE │
│ │
│ Drupal Canvas │
│ SDC / Code Components │
│ @drupal-canvas/cli │
│ dashboards / charts where appropriate │
├───────────────────────────────────────────────────────────────────────────┤
│ GOVERNED CONTEXT │
│ │
│ drupal/ai_context │
│ ContextControl.ai │
│ recipe_contextual_memory │
│ kb_cache governance extensions │
├───────────────────────────────────────────────────────────────────────────┤
│ EXECUTABLE DRUPAL CAPABILITY │
│ │
│ drupal/tool │
│ drupal/tool_belt │
│ Drupal AI / AI Agents │
│ Drupal MCP where appropriate │
├───────────────────────────────────────────────────────────────────────────┤
│ WORKFLOW & INTEGRATION │
│ │
│ drupal/eca │
│ drupal/ai_integration_eca │
│ drupal/api_orchestrator │
│ drupal/webhooks │
│ drupal/key │
├───────────────────────────────────────────────────────────────────────────┤
│ GOVERNANCE / AUTHORIZATION │
│ │
│ ContextControl = human review / curation │
│ Cedar = mutation authorization │
├───────────────────────────────────────────────────────────────────────────┤
│ FACTORY ORCHESTRATION │
│ │
│ Gas City │
│ Beads │
│ authoritative Dolt work graph │
│ Packs / Formulas / Orders / Events │
├───────────────────────────────────────────────────────────────────────────┤
│ DELIVERY │
│ │
│ GitLab │
│ gitlab_components │
│ DDEV │
│ governed worktrees │
│ release/v0.1.x → main │
└───────────────────────────────────────────────────────────────────────────┘
22. Drupal Does Not Become the Factory Control Plane¶
Permanent boundary:
DRUPAL = CONTENT = CONTEXT = GOVERNANCE UI = WORKFLOW = TOOL EXECUTION = HUMAN REVIEW
GAS CITY = AGENT ORCHESTRATION
BEADS = DURABLE WORK
CEDAR = AUTHORIZATION
GITLAB = SOURCE / CI / RELEASE
ORACLE = EXECUTION RUNTIME
Drupal must not recreate: - Gas City orchestration - Beads work graph - GitLab pipeline logic - host provisioning - Tailscale management - container lifecycle control - generic secret management - generic model routing
23. Gas City Integration Boundary¶
Gas City Beads are a pluggable persistence boundary. The default bd provider uses the Dolt-backed Beads implementation, while Gas City can also delegate the Bead store to an exec provider.
The exec-provider contract supports:
create get update close reopen delete list children ready metadata dependencies
That means Drupal should integrate through governed Gas City/Beads interfaces rather than implementing its own competing durable work graph.
Correct:
Drupal event → governance → Gas City Order/API → Bead → agent execution → event/result → Drupal receipt
Incorrect:
Drupal tables → recreate Beads → recreate agent scheduler → recreate task dependencies
24. ContextControl ↔ Gas City Contract¶
Target:
DRUPAL EVENT
↓
ECA / Tool API
↓
ContextControl governance
↓
Cedar authorization
↓
Gas City Order
↓
Bead
↓
Agent
↓
Result / Event
↓
ContextControl receipt
Correlation must survive the entire lifecycle:
correlation_id
request_id
bead_id
rig
agent
source_entity
source_revision
result
provenance
ContextControl stores governance and receipt information. It does not become the execution scheduler.
25. Upstream Contribution Before Permanent Fork¶
If upstream almost provides what Bluefly needs:
OPEN ISSUE → PROPOSE FIX → CONTRIBUTE MR → TEMPORARY THIN ADAPTER IF NECESSARY → REMOVE ADAPTER WHEN UPSTREAM LANDS
Do not immediately create bluefly_<upstream_project> and maintain a permanent parallel implementation.
A fork requires:
UPSTREAM_GAP_PROVEN=YES
CONTRIBUTION_PATH_EXHAUSTED=YES
BUSINESS_NEED=YES
EXIT_PLAN=YES
FOUNDER_EXCEPTION=YES
26. Version Law¶
Do not hardcode dependency versions in architecture prose unless the version itself is part of the architecture.
Use:
CURRENT_SUPPORTED_STABLE
composer show drupal/<project> --all
Examples as of September 2026: - Canvas: 1.11.x stable - AI Context / CCC: 1.0 beta line - AI Integration - ECA: 1.0 stable - API Orchestrator: 1.1 stable - Key: 1.22 stable - Tool Belt: 1.0 alpha line
These are observations, not eternal architecture. Treat upstream release status as dynamic truth.
27. Dependency Selection Rule¶
A module being available does not mean Bluefly should install it.
Classify:
CAPABILITY_REQUIRED=YES|NO
UPSTREAM_MAINTENANCE=
SECURITY_COVERAGE=
STABLE_RELEASE=
OVERLAPPING_OWNER=
ACTIVE_CONSUMER=
Prefer stable/security-covered releases. Alpha/beta dependencies require an explicit reason.
28. DDEV Is the Standard Local Drupal Runtime¶
Bluefly Drupal development happens in DDEV.
Normal flow:
BEAD → GOVERNED WORKTREE → DDEV → COMPOSER INSTALL → DRUSH → TESTS → CONFIG/RECIPE VALIDATION → PUSH → GITLAB MR
Host-local assumptions do not become project requirements. No developer should need Thomas's machine-specific filesystem state to build the site.
29. Composer Projection Rule¶
Anything under:
web/modules/contrib/
web/themes/contrib/
vendor/
recipes/contrib/
Do not edit the projection.
Find:
PACKAGE OWNER
SOURCE REPOSITORY
RECIPE OWNER
COMPOSER CONSTRAINT
Permanent law:
Do not edit what Composer installed. Edit what Composer installs from.
30. Bluefly Module Ownership¶
Custom Bluefly modules exist only for Bluefly-specific capability.
Valid examples: - governance semantics - provenance - Bluefly domain model - Cedar integration - cross-system correlation - product-specific UI - product-specific policy
Invalid reasons:
- "we needed an HTTP client"
- "we needed CRUD"
- "we needed a webhook"
- "we needed a field"
- "we needed a queue"
- "we needed an agent tool"
- "we needed vector storage"
- "we needed a migration" (core Migrate + migrate_plus own it; see Playbooks/drupal/migration-factory-drupal.md)
- "we needed a dashboard" (ai_dashboard, ai_metering, ai_logging own AI administration)
- "we needed an agent-readable copy of content" (markdownify + llms_txt)
Those require upstream discovery first.
31. The Definition of Done¶
Drupal work is not done because: - MODULE EXISTS - CONFIG IMPORTS - DRUSH COMMAND PASSES - PAGE LOADS - MR IS GREEN
Applicable completion chain:
UPSTREAM OWNER VERIFIED
↓
IMPLEMENTED AT CORRECT LAYER
↓
CONFIGURED
↓
TESTED
↓
RECIPE / CONFIG REPRODUCIBLE
↓
CLEAN INSTALL PROVEN
↓
CONSUMER PROVEN
↓
MR MERGED TO RELEASE
↓
RELEASE READBACK
↓
DEPLOYED
↓
RUNTIME ACCEPTED
↓
WITNESS VERIFIED
32. Permanent Drupal Operating Law¶
SEARCH BEFORE BUILDING.
CORE BEFORE CONTRIB.
CONTRIB BEFORE CUSTOM.
CONFIG BEFORE PHP.
RECIPE BEFORE INSTALL PROFILE LOGIC.
ECA BEFORE CUSTOM WORKFLOW CODE.
TOOL API BEFORE CUSTOM EXECUTION API.
TOOL BELT BEFORE REIMPLEMENTING DRUPAL OPERATIONS.
API ORCHESTRATOR BEFORE CUSTOM HTTP CLIENTS.
EXISTING WEBHOOK OWNERS BEFORE NEW RECEIVERS.
AI CONTEXT BEFORE CUSTOM CONTEXT MODELS.
DRUPAL AI PROVIDERS BEFORE CUSTOM VECTOR CLIENTS.
CANVAS BEFORE CUSTOM PAGE-BUILDER INFRASTRUCTURE.
KEY REFERENCES SECRETS.
1PASSWORD OWNS SECRET VALUES.
DRUPAL GOVERNS CONTENT AND CONTEXT.
CEDAR AUTHORIZES MUTATION.
GAS CITY ORCHESTRATES AGENTS.
BEADS OWNS DURABLE WORK.
GITLAB OWNS SOURCE.
COMPOSER OWNS DEPENDENCY PROJECTION.
DO NOT EDIT CONTRIB PROJECTIONS.
DO NOT CREATE A MODULE TO AVOID CONFIGURATION.
DO NOT CREATE AN INTEGRATION TO AVOID READING UPSTREAM.
DO NOT FREEZE TOOL COUNTS INTO ARCHITECTURE.
DO NOT FREEZE OLD RELEASE NUMBERS INTO ARCHITECTURE.
DO NOT CREATE ANOTHER OWNER WHEN ONE ALREADY EXISTS.
REUSE THE OWNER.
FIX THE OWNER.
EXTEND THE OWNER.
CONTRIBUTE UPSTREAM.
OWN LESS.
SHIP MORE.