Background
local-stack is a Swarm-first infrastructure repository. Automated dependency updates are useful, but Docker image bumps can break a running host even when CI passes. The current pain point is that Dependabot can raise updates that are auto-merged without enough runtime risk context, including major image bumps.
This issue introduces a guarded Dependabot workflow where Docker and GitHub Actions dependency updates are classified before merge. The classifier must use OpenCode via anomalyco/opencode/github@main, produce a deterministic PR comment, and only approve low-risk updates. Major updates and unknown-risk updates must require human review.
The deployment branch for this repository is dev.
Goals
- Add Dependabot configuration for Docker and GitHub Actions updates targeting
dev.
- Group patch/minor updates separately from major updates.
- Add an OpenCode-based risk gate for Dependabot PRs.
- Produce a deterministic PR review comment for every Dependabot PR handled by the gate.
- Approve low-risk patch/minor updates when validation passes.
- Request changes or block auto-merge for major, unknown-risk, stateful, or runtime-sensitive updates.
- Prevent the OpenCode gate from deploying anything.
- Prevent production/deployment secrets from being exposed to PR workflows.
Non-goals
- Do not configure Doco-CD in this issue.
- Do not deploy to the host from GitHub Actions.
- Do not use Watchtower as the primary update mechanism.
- Do not allow AI output to directly merge without schema validation and normal required checks.
Required Dependabot configuration
Add .github/dependabot.yml.
Expected baseline:
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
time: "03:00"
timezone: "Africa/Johannesburg"
target-branch: "dev"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "docker"
- "automation"
commit-message:
prefix: "chore"
include: "scope"
groups:
docker-patch-minor:
update-types:
- "minor"
- "patch"
docker-major:
update-types:
- "major"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "saturday"
time: "03:30"
timezone: "Africa/Johannesburg"
target-branch: "dev"
open-pull-requests-limit: 5
labels:
- "dependencies"
- "github-actions"
- "automation"
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions-patch-minor:
update-types:
- "minor"
- "patch"
github-actions-major:
update-types:
- "major"
Implementation must verify whether Dependabot updates the service-level docker-compose.yml files, generated stacks/*.yml, or both. The source of truth is the service-level Compose files plus swarm.fragment.yml; generated stacks/*.yml must not become the only modified files. If Dependabot modifies generated stack files without the corresponding source change, the PR must be treated as invalid.
Required validation workflow
Add .github/workflows/validate-stack.yml.
This workflow must run on pull_request targeting dev and push to dev.
It must:
- Check out the code.
- Install Python and
tools/requirements.txt.
- Run
./stackctl.sh sync.
- Run
./stackctl.sh up --dry-run --no-logs.
This workflow must not use production secrets.
Expected baseline:
name: Validate stack
on:
pull_request:
branches:
- dev
push:
branches:
- dev
permissions:
contents: read
jobs:
stack:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install render dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r tools/requirements.txt
- name: Check generated stack drift
run: ./stackctl.sh sync
- name: Dry-run stack rendering
run: ./stackctl.sh up --dry-run --no-logs
Required OpenCode gate workflow
Add .github/workflows/dependabot-opencode-gate.yml.
The gate must:
- Run only for
dependabot[bot] PRs.
- Use
pull_request_target only for metadata/comment/review operations.
- Avoid checking out and executing PR code in
pull_request_target.
- Fetch PR metadata and diff using GitHub API/CLI.
- Run OpenCode via
anomalyco/opencode/github@main.
- Use Context7 MCP or an equivalent documentation/release-note retrieval path where available.
- Validate OpenCode output against a strict JSON schema before commenting or applying review decisions.
- Post or update a deterministic PR comment.
- Approve low-risk updates.
- Request changes for high-risk updates.
- Return neutral/no-auto-merge for unknown or mixed-risk updates.
Required workflow shape:
name: Dependabot OpenCode gate
on:
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
permissions:
contents: read
pull-requests: write
issues: write
checks: read
jobs:
gate:
if: github.event.pull_request.user.login == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@<pin-sha>
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Collect PR changed files and patch metadata
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
mkdir -p .review
gh pr diff "$PR" --repo "$REPO" --name-only > .review/files.txt
gh pr diff "$PR" --repo "$REPO" > .review/patch.diff
gh pr view "$PR" --repo "$REPO" --json title,body,author,baseRefName,headRefName,labels > .review/pr.json
- name: Build deterministic review context
run: ./tools/ci/build-dependabot-review-context.sh
- name: Run OpenCode review
id: opencode
uses: anomalyco/opencode/github@main
with:
# Confirm exact input names against the action before implementation.
prompt-file: .github/prompts/dependabot-docker-risk-review.md
context-file: .review/context.json
output-file: .review/opencode-result.json
mcp-config-file: .github/opencode/mcp.json
- name: Validate OpenCode output schema
run: python tools/ci/validate-opencode-risk-output.py .review/opencode-result.json
- name: Comment review result
run: python tools/ci/comment-opencode-risk-review.py .review/opencode-result.json
- name: Apply PR review decision
run: python tools/ci/apply-opencode-review-decision.py .review/opencode-result.json
The exact anomalyco/opencode/github@main input names must be verified during implementation. Do not leave this workflow broken with guessed inputs.
Required prompt and output schema
Add:
.github/prompts/dependabot-docker-risk-review.md
.github/opencode/mcp.json
.github/opencode/risk-output.schema.json
tools/ci/build-dependabot-review-context.sh
tools/ci/validate-opencode-risk-output.py
tools/ci/comment-opencode-risk-review.py
tools/ci/apply-opencode-review-decision.py
The OpenCode output must validate to this shape:
{
"schema_version": "local-stack.dependabot-risk.v1",
"decision": "approve | changes_requested | neutral",
"risk": "low | medium | high | unknown",
"automerge_allowed": true,
"dependency_ecosystem": "docker | github-actions | mixed | unknown",
"update_type": "semver-patch | semver-minor | semver-major | digest | non-semver | unknown",
"changed_images": [
{
"name": "apache/apisix",
"from": "3.16.0-debian",
"to": "3.17.0-debian",
"risk_reason": "minor update; release notes checked; no breaking config migration found"
}
],
"changed_files": [
"apisix/api-gateway/docker-compose.yml"
],
"summary": "One paragraph summary.",
"breaking_change_assessment": "No breaking changes found in checked release notes.",
"runtime_impact": "Expected rolling service update only.",
"required_checks": [
"./stackctl.sh sync",
"./stackctl.sh up --dry-run --no-logs"
],
"manual_follow_up": [],
"sources_checked": [
"Dependabot metadata",
"PR diff",
"Context7 package/release notes"
],
"confidence": "high | medium | low"
}
Decision policy
Approve only when all are true:
- The PR is from
dependabot[bot].
- The update is patch/minor or digest-only.
- Validation workflow passes.
- OpenCode classifies risk as low.
- Changed files are limited to expected dependency files.
- No source/generated stack drift is present.
- No stateful major image migration is detected.
Return neutral/no-auto-merge when:
- The update is mixed-risk.
- OpenCode confidence is low.
- Release-note context is missing but no direct blocker is found.
Request changes when:
- Any Docker image update is major.
- Any stateful service image has migration/storage risk.
- Any gateway/proxy image has breaking config risk.
- The PR touches
.env.example, .env.enc, .sops.yaml, stackctl.sh, tools/render_compose.py, or tools/generate_stacks.py.
- Generated
stacks/*.yml are changed without source Compose/fragment changes.
- OpenCode cannot classify a runtime-sensitive update.
PR comment format
The comment must use this stable format:
## OpenCode Dependabot Risk Review
Schema: `local-stack.dependabot-risk.v1`
Decision: `APPROVE | CHANGES_REQUESTED | NEUTRAL`
Risk: `LOW | MEDIUM | HIGH | UNKNOWN`
Automerge allowed: `true | false`
Update type: `<type>`
Ecosystem: `<ecosystem>`
### Summary
<one paragraph>
### Changed runtime artifacts
| Image / dependency | From | To | Risk note |
|---|---:|---:|---|
| `apache/apisix` | `3.16.0-debian` | `3.17.0-debian` | Patch/minor update; no breaking config migration found. |
### Runtime impact
<short paragraph>
### Required checks
- [ ] `./stackctl.sh sync`
- [ ] `./stackctl.sh up --dry-run --no-logs`
### Manual follow-up
None or bullet list.
### Sources checked
- Dependabot metadata
- PR diff
- Context7 release/package context
Acceptance criteria
Implementation notes
Patch/minor Docker image updates may auto-merge and auto-deploy after all required checks pass. Major updates require manual promotion and must not be auto-approved.
This issue intentionally stops at PR gating. Doco-CD deployment is covered by a separate issue.
Background
local-stackis a Swarm-first infrastructure repository. Automated dependency updates are useful, but Docker image bumps can break a running host even when CI passes. The current pain point is that Dependabot can raise updates that are auto-merged without enough runtime risk context, including major image bumps.This issue introduces a guarded Dependabot workflow where Docker and GitHub Actions dependency updates are classified before merge. The classifier must use OpenCode via
anomalyco/opencode/github@main, produce a deterministic PR comment, and only approve low-risk updates. Major updates and unknown-risk updates must require human review.The deployment branch for this repository is
dev.Goals
dev.Non-goals
Required Dependabot configuration
Add
.github/dependabot.yml.Expected baseline:
Implementation must verify whether Dependabot updates the service-level
docker-compose.ymlfiles, generatedstacks/*.yml, or both. The source of truth is the service-level Compose files plusswarm.fragment.yml; generatedstacks/*.ymlmust not become the only modified files. If Dependabot modifies generated stack files without the corresponding source change, the PR must be treated as invalid.Required validation workflow
Add
.github/workflows/validate-stack.yml.This workflow must run on
pull_requesttargetingdevandpushtodev.It must:
tools/requirements.txt../stackctl.sh sync../stackctl.sh up --dry-run --no-logs.This workflow must not use production secrets.
Expected baseline:
Required OpenCode gate workflow
Add
.github/workflows/dependabot-opencode-gate.yml.The gate must:
dependabot[bot]PRs.pull_request_targetonly for metadata/comment/review operations.pull_request_target.anomalyco/opencode/github@main.Required workflow shape:
The exact
anomalyco/opencode/github@maininput names must be verified during implementation. Do not leave this workflow broken with guessed inputs.Required prompt and output schema
Add:
.github/prompts/dependabot-docker-risk-review.md.github/opencode/mcp.json.github/opencode/risk-output.schema.jsontools/ci/build-dependabot-review-context.shtools/ci/validate-opencode-risk-output.pytools/ci/comment-opencode-risk-review.pytools/ci/apply-opencode-review-decision.pyThe OpenCode output must validate to this shape:
{ "schema_version": "local-stack.dependabot-risk.v1", "decision": "approve | changes_requested | neutral", "risk": "low | medium | high | unknown", "automerge_allowed": true, "dependency_ecosystem": "docker | github-actions | mixed | unknown", "update_type": "semver-patch | semver-minor | semver-major | digest | non-semver | unknown", "changed_images": [ { "name": "apache/apisix", "from": "3.16.0-debian", "to": "3.17.0-debian", "risk_reason": "minor update; release notes checked; no breaking config migration found" } ], "changed_files": [ "apisix/api-gateway/docker-compose.yml" ], "summary": "One paragraph summary.", "breaking_change_assessment": "No breaking changes found in checked release notes.", "runtime_impact": "Expected rolling service update only.", "required_checks": [ "./stackctl.sh sync", "./stackctl.sh up --dry-run --no-logs" ], "manual_follow_up": [], "sources_checked": [ "Dependabot metadata", "PR diff", "Context7 package/release notes" ], "confidence": "high | medium | low" }Decision policy
Approve only when all are true:
dependabot[bot].Return neutral/no-auto-merge when:
Request changes when:
.env.example,.env.enc,.sops.yaml,stackctl.sh,tools/render_compose.py, ortools/generate_stacks.py.stacks/*.ymlare changed without source Compose/fragment changes.PR comment format
The comment must use this stable format:
Acceptance criteria
.github/dependabot.ymlexists and targetsdev.Validate stackworkflow runs./stackctl.sh syncand./stackctl.sh up --dry-run --no-logs.CHANGES_REQUESTED.APPROVEwhen validation passes.Implementation notes
Patch/minor Docker image updates may auto-merge and auto-deploy after all required checks pass. Major updates require manual promotion and must not be auto-approved.
This issue intentionally stops at PR gating. Doco-CD deployment is covered by a separate issue.