ADR-018: Fork-Resources Staging Pattern for Specialized Template Deployment
Status
Accepted
Context
Some template resources need deployment handling beyond copying a file to the same path:
- Issue templates: should exist in forks but not be overwritten by every template sync
- Copilot configuration: instructions and firewall settings deployed to
.github/, with a repository variable set from the firewall file - Prompt files: copied into
.github/prompts/ - Service-specific configuration:
dependabot.yml,upstream-filter.yml, andCODEOWNERScarry a<service>placeholder substituted at deployment
The existing sync mechanisms (ADR-011, ADR-012) handle direct sync (same path in template and fork) and workflow templates (template-workflows/ to .github/workflows/). They do not cover multi-target deployment, conditional processing, substitution, or staging directories that must not exist in forks.
Decision
Establish .github/fork-resources/ as a staging area for template resources that require specialized deployment.
Architecture
Template Repository:
├── .github/
│ ├── fork-resources/ # Staging area (template only)
│ │ ├── CODEOWNERS # → substituted to .github/CODEOWNERS
│ │ ├── CONTRIBUTING.md # → copied to CONTRIBUTING.md
│ │ ├── ISSUE_TEMPLATE/ # → copied to .github/ISSUE_TEMPLATE/
│ │ ├── copilot-instructions.md # → copied to .github/copilot-instructions.md
│ │ ├── copilot-firewall-config.json # → copied to .github/ + repository variable
│ │ ├── dependabot.yml # → substituted to .github/dependabot.yml
│ │ ├── triage.prompt.md # → copied to .github/prompts/
│ │ └── upstream-filter.yml # → substituted to .github/upstream-filter.yml
│ └── sync-config.json # Includes fork-resources in sync rules
Fork Repository (after deployment):
├── .github/
│ ├── ISSUE_TEMPLATE/ # Final location
│ ├── copilot-instructions.md # Final location
│ ├── prompts/ # Final location
│ └── (no fork-resources/) # Staging area removed
Deployment mechanisms
- Initialization (
init-complete.yml):init-helpers/deploy-fork-resources.shcopies each resource to its final location, sets the Copilot firewall repository variable fromcopilot-firewall-config.json, and removesfork-resources/ - Update (
sync-template.yml): whenfork-resourceschanges in the template, the sync re-copies the resources to their final locations and removes the staging directory again - Sync configuration:
.github/fork-resourcesis listed insync_rules.directorieswithsync_all: true, so changes are detected by template sync
Pattern rules
- Staging only:
fork-resources/exists only in the template repository, never in forks - Specialized logic: each resource type can have its own deployment step
- Cleanup required: deployment must remove
fork-resources/after processing - Sync integration: changes to
fork-resourcesflow through the normal template sync - Service substitution: a resource may carry a
<service>placeholder, replaced at deployment with the service slug derived fromUPSTREAM_REPO_URL(the URL basename).dependabot.ymluses this today.upstream-filter.ymltakes itsservicefrom the upstreamprovider/<prefix>-azuremodule at initialization and falls back to the basename, since the module prefix can differ from the slug.CODEOWNERSreads the prefix from the fork's ownprovider/<prefix>-azuretree, falling back to the filter'sservicekey, and takes<owners>from theCODEOWNERSrepository variable. There is no default: a single person as sole owner could not approve their own pull requests, so an unset variable leaves the file unplanted andsettings-apply.ymlreports it. - Fork-owned after planting:
upstream-filter.yml,CODEOWNERSandCONTRIBUTING.mddeploy create-if-missing only.CONTRIBUTING.mdis the exception at initialization, where it replaces the template's own contributing guide. Once planted they belong to the fork, and template sync never overwrites them. A fork missingCODEOWNERSwith the variable set receives it on the next sync run whether or not the template changed.
Alternatives Considered
- Direct sync: rejected; issue templates would be overwritten on every sync, and there is no way to deploy one source to a different path or to substitute values.
- Hardcoded deployment in workflows: rejected; every new resource type would require a workflow change.
Consequences
Resources are maintained in the staging directory rather than at their final path, and each resource type needs its own deployment step. Deployment logic for similar resources can diverge, and a missed cleanup would leave the staging directory in a fork.
Related ADRs
- ADR-011: Configuration-Driven Template Synchronization (adds
fork-resourcesto the sync rules) - ADR-012: Template Update Propagation Strategy (redeploys resources during template sync)
- ADR-038: Upstream Filter Transform (consumer of
upstream-filter.yml)