diff --git a/README.md b/README.md index 9ee40077..cd65f30f 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Current Socket catalog shape: - `apple-creator-studio-skills`: source-preserving Final Cut Pro editing, Motion template, Compressor delivery, Logic Pro production, MainStage concert, and GarageBand project workflows with local Help Viewer discovery, explicit Computer Use safeguards, and artifact or rehearsal verification - `apple-dev-skills`: Apple, Swift, SwiftPM, macOS-hosted boundary selection, custom Virtualization framework hosts, persistent Linux development guests, clean macOS development guests, imaging, Vision/Core ML, camera, spatial sensing, media/audio, SwiftUI, AppKit, Xcode, Safari, OpenAPI, and DocC workflows, plus the source-bundled `swift-steward` custom-agent definition with its own roadmap - `cardhop-app`: mixed skill plus bundled MCP server for Cardhop.app contact workflows -- `cloud-deployment-skills`: cloud provider deployment routing, official provider plugin selection, credential and mutation boundary checks, AWS handoff to the official AWS Agent Toolkit, and Azure handoff to Microsoft's Azure Skills plugin rather than duplicated provider MCP or CLI setup +- `cloud-deployment-skills`: cloud provider deployment routing, official provider plugin selection, credential and mutation boundary checks, a reusable Dockerized-service release/deployment contract, AWS handoff to the official AWS Agent Toolkit, and Azure handoff to Microsoft's Azure Skills plugin rather than duplicated provider MCP or CLI setup - `cloud-inference-skills`: cloud AI inference, training, model conversion, and GPU infrastructure routing for Runpod, Hugging Face, AWS, Vast.ai, CoreWeave, and similar providers, with bundled Runpod MCP server configuration, upstream Runpod skill mirrors, and first-party Hugging Face/AWS handoffs - `cybersecurity-skills`: suspicious-content triage, evidence preservation, isolation selection, disposable Linux and macOS analysis-lab preparation, malware analysis, agentic security-tool controls, macOS investigation and defense, vulnerability validation, authorized testing, incident response, threat hunting, detection content, and clear non-specialist advice - `messaging-collaboration-skills`: chat-app, bot, business-messaging, meeting-collaboration, iMessage collaboration, Communication Notifications, Push to Talk, VoIP/SIP, documented iOS/iPadOS default communication roles, and app-owned macOS client workflows for Discord, Telegram, Slack, Teams, WhatsApp Business, SMS/MMS/RCS, Google Meet, and Apple communication surfaces, with explicit Signal and Mac operator-automation boundaries diff --git a/ROADMAP.md b/ROADMAP.md index bead77d1..ba42d846 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -713,7 +713,7 @@ Completed - [x] The plugin boundary is clear enough for future provider slices without absorbing framework-specific deployment workflows. - [x] Root Socket docs, marketplace wiring, and validation agree on the exported cloud-deployment skill surface. -Completed Milestone 21 by adding the `cloud-deployment-skills` child plugin, shipping the provider-routing workflow, wiring the Socket marketplace entry, documenting the AWS Agent Toolkit delegation, and keeping future provider expansion scoped to small official-tool routing slices. +Completed Milestone 21 by adding the `cloud-deployment-skills` child plugin, shipping the provider-routing workflow, wiring the Socket marketplace entry, documenting the AWS Agent Toolkit delegation, and keeping future provider expansion scoped to small official-tool routing slices. The plugin now also ships a provider-neutral Dockerized-service release/deployment contract and templates: clean GitHub Actions artifact builds, main-anchored tags, immutable digest manifests, release-published production deployment triggers, environment approval, exact-digest health checks, and rollback handoffs without host-side rebuilds. ## Milestone 22: Network Protocol Skills plugin diff --git a/docs/maintainers/hermes-compatibility.md b/docs/maintainers/hermes-compatibility.md index 6f1fa7fb..b21145f8 100644 --- a/docs/maintainers/hermes-compatibility.md +++ b/docs/maintainers/hermes-compatibility.md @@ -112,9 +112,11 @@ plugin runtime, MCP server, or native Hermes extension. The exported F# web-framework workflows are portable instruction contracts. They use the operator's .NET SDK and project dependencies but bundle no runtime, framework package, MCP server, hook, or native Hermes extension. The cloud -deployment routing workflow is also portable guidance: it routes Azure work to -Microsoft's official Azure Skills plugin but does not claim that Azure MCP is -automatically installed or configured in Hermes. +deployment routing workflow and Dockerized-service release/deployment workflow +are also portable guidance: they route Azure work to Microsoft's official Azure +Skills plugin and define a clean-CI, exact-digest release contract, but do not +claim that Azure MCP, provider credentials, or production deployment adapters +are automatically installed or configured in Hermes. The exported Python workflows are portable guidance. They guide `uv`-managed agent services, FastAPI services, FastMCP services, and tests while relying on diff --git a/plugins/agent-portability-skills/.codex-plugin/plugin.json b/plugins/agent-portability-skills/.codex-plugin/plugin.json index b9591eb0..2839541a 100644 --- a/plugins/agent-portability-skills/.codex-plugin/plugin.json +++ b/plugins/agent-portability-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agent-portability-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Maintainer skills for Socket-owned agent skill portability, Codex plugin surfaces, and host adapter guidance.", "author": { "name": "Gale", diff --git a/plugins/agent-portability-skills/pyproject.toml b/plugins/agent-portability-skills/pyproject.toml index 73f14565..2a3f62e6 100644 --- a/plugins/agent-portability-skills/pyproject.toml +++ b/plugins/agent-portability-skills/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-portability-skills-maintenance" -version = "9.26.1" +version = "9.27.0" description = "Maintainer-only Python tooling baseline for Agent Portability Skills." requires-python = ">=3.11" dependencies = [] diff --git a/plugins/agent-portability-skills/uv.lock b/plugins/agent-portability-skills/uv.lock index 3d9ea633..467cd975 100644 --- a/plugins/agent-portability-skills/uv.lock +++ b/plugins/agent-portability-skills/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "agent-portability-skills-maintenance" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies] diff --git a/plugins/agentdeck/.codex-plugin/plugin.json b/plugins/agentdeck/.codex-plugin/plugin.json index 1f85d52f..e14fcbde 100644 --- a/plugins/agentdeck/.codex-plugin/plugin.json +++ b/plugins/agentdeck/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "agentdeck", - "version": "9.26.1", + "version": "9.27.0", "description": "Local Codex runtime utilities for thread, hook, and app-server workflows.", "author": { "name": "Gale", diff --git a/plugins/android-dev-skills/.codex-plugin/plugin.json b/plugins/android-dev-skills/.codex-plugin/plugin.json index 4d328bdb..f00ad038 100644 --- a/plugins/android-dev-skills/.codex-plugin/plugin.json +++ b/plugins/android-dev-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "android-dev-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Android, Kotlin, Java, Gradle, Android Gradle Plugin, testing, lint, UI implementation, and release-readiness workflow skills.", "author": { "name": "Gale", diff --git a/plugins/apple-creator-studio-skills/.codex-plugin/plugin.json b/plugins/apple-creator-studio-skills/.codex-plugin/plugin.json index cd8c9f72..371ccb4a 100644 --- a/plugins/apple-creator-studio-skills/.codex-plugin/plugin.json +++ b/plugins/apple-creator-studio-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "apple-creator-studio-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Human-facing and Computer Use-aware Apple Creator Studio workflows for Final Cut Pro editing, Motion templates, Compressor delivery, Logic Pro production, MainStage concert preparation, and GarageBand projects.", "author": { "name": "Gale", diff --git a/plugins/apple-dev-skills/.codex-plugin/plugin.json b/plugins/apple-dev-skills/.codex-plugin/plugin.json index 57332754..64ee71b5 100644 --- a/plugins/apple-dev-skills/.codex-plugin/plugin.json +++ b/plugins/apple-dev-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "apple-dev-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Apple development workflows for Codex, including macOS privacy permissions, sandbox file access, entitlement diagnosis, virtualization, SwiftPM, Xcode, app extensions, media, provisioning, SwiftUI, AppKit, Safari, security, OpenAPI, and DocC.", "author": { "name": "Gale", diff --git a/plugins/apple-dev-skills/pyproject.toml b/plugins/apple-dev-skills/pyproject.toml index 1d395013..85c2351e 100644 --- a/plugins/apple-dev-skills/pyproject.toml +++ b/plugins/apple-dev-skills/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "apple-dev-skills-maintainer" -version = "9.26.1" +version = "9.27.0" description = "Maintainer tooling for the apple-dev-skills repository" requires-python = ">=3.10" dependencies = [] diff --git a/plugins/apple-dev-skills/uv.lock b/plugins/apple-dev-skills/uv.lock index 20b4b615..d99f3b85 100644 --- a/plugins/apple-dev-skills/uv.lock +++ b/plugins/apple-dev-skills/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.10" [[package]] name = "apple-dev-skills-maintainer" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies] diff --git a/plugins/cardhop-app/.codex-plugin/plugin.json b/plugins/cardhop-app/.codex-plugin/plugin.json index 1f4e6c83..0a366308 100644 --- a/plugins/cardhop-app/.codex-plugin/plugin.json +++ b/plugins/cardhop-app/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "cardhop-app", - "version": "9.26.1", + "version": "9.27.0", "description": "Cardhop.app workflow guidance plus a bundled local MCP server for contact capture and updates on macOS.", "author": { "name": "Gale", diff --git a/plugins/cardhop-app/mcp/pyproject.toml b/plugins/cardhop-app/mcp/pyproject.toml index f1e2186c..89805434 100644 --- a/plugins/cardhop-app/mcp/pyproject.toml +++ b/plugins/cardhop-app/mcp/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "cardhop-app-mcp" -version = "9.26.1" +version = "9.27.0" requires-python = ">=3.13" dependencies = [ "fastmcp>=3.0.2", diff --git a/plugins/cardhop-app/mcp/uv.lock b/plugins/cardhop-app/mcp/uv.lock index 1026583e..d915b269 100644 --- a/plugins/cardhop-app/mcp/uv.lock +++ b/plugins/cardhop-app/mcp/uv.lock @@ -141,7 +141,7 @@ wheels = [ [[package]] name = "cardhop-app-mcp" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } dependencies = [ { name = "fastmcp" }, diff --git a/plugins/cloud-deployment-skills/.codex-plugin/plugin.json b/plugins/cloud-deployment-skills/.codex-plugin/plugin.json index ec3e042c..fdac3e61 100644 --- a/plugins/cloud-deployment-skills/.codex-plugin/plugin.json +++ b/plugins/cloud-deployment-skills/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "cloud-deployment-skills", - "version": "9.26.1", - "description": "Codex skills for routing cloud deployment work through official provider plugins, MCP servers, CLIs, and Socket-owned deployment guidance.", + "version": "9.27.0", + "description": "Codex skills for routing cloud deployment work through official provider plugins, MCP servers, CLIs, and a reusable Dockerized-service release and deployment contract.", "author": { "name": "Gale", "email": "mail@galewilliams.com", @@ -23,13 +23,18 @@ "mcp", "cloudflare", "vercel", - "fly.io" + "fly.io", + "docker", + "oci", + "github-actions", + "github-releases", + "supply-chain-security" ], "skills": "./skills/", "interface": { "displayName": "Cloud Deployment Skills", "shortDescription": "Cloud provider and deployment routing guidance for Codex.", - "longDescription": "Guide Codex agents through cloud deployment routing, official provider plugin selection, credential and mutation safety checks, and provider handoffs. Socket delegates AWS work to the official AWS Agent Toolkit and Azure work to Microsoft's Azure Skills plugin, keeping Socket focused on thin cross-provider decisions instead of duplicating provider MCP, CLI, or deployment workflows.", + "longDescription": "Guide Codex agents through cloud deployment routing, official provider plugin selection, credential and mutation safety checks, and a reusable Dockerized-service release contract: clean GitHub Actions builds, main-anchored tags, immutable image digests, release manifests, release-published production deployment, approval, health checks, and exact-digest rollback. Socket delegates AWS work to the official AWS Agent Toolkit and Azure work to Microsoft's Azure Skills plugin, keeping Socket focused on thin cross-provider decisions instead of duplicating provider MCP, CLI, or deployment workflows.", "developerName": "gaelic-ghost", "category": "Developer Tools", "capabilities": [ @@ -39,7 +44,8 @@ "websiteURL": "https://github.com/gaelic-ghost/socket/tree/main/plugins/cloud-deployment-skills", "defaultPrompt": [ "Choose the right cloud deployment path and route AWS or Azure work through the applicable official provider plugin when appropriate.", - "Decide whether this deployment should use an official provider plugin, provider CLI, MCP server, framework-owned deploy workflow, or a Socket-owned provider skill." + "Decide whether this deployment should use an official provider plugin, provider CLI, MCP server, framework-owned deploy workflow, or a Socket-owned provider skill.", + "Set up or audit a Dockerized backend release path that builds in clean GitHub Actions, deploys only a published release manifest digest after production approval, verifies health, and rolls back by exact digest." ], "brandColor": "#22D3EE", "composerIcon": "./assets/cloud-deployment-icon.svg", diff --git a/plugins/cloud-deployment-skills/AGENTS.md b/plugins/cloud-deployment-skills/AGENTS.md index 487cce92..23c77fb3 100644 --- a/plugins/cloud-deployment-skills/AGENTS.md +++ b/plugins/cloud-deployment-skills/AGENTS.md @@ -8,6 +8,7 @@ This file is the Cloud Deployment Skills child-repo override for work done from - Root [`skills/`](./skills/) is the authored workflow surface. - The repo root is the Codex plugin root through [`.codex-plugin/plugin.json`](./.codex-plugin/plugin.json). - Keep this plugin focused on provider selection, official-tool routing, deployment readiness, and cross-provider handoffs. +- Own the provider-neutral release contract for Dockerized backend and cloud services: clean GitHub Actions builds, main-anchored tags, immutable image digests, release manifests, release-published deployment triggers, production approval, exact-digest deployment, health verification, and rollback handoff. - Keep framework-specific application implementation in the owning stack plugins such as `server-side-swift`, `web-dev-skills`, `python-skills`, `dotnet-skills`, `rust-skills`, and `server-side-jvm`. ## Local Rules @@ -22,3 +23,4 @@ This file is the Cloud Deployment Skills child-repo override for work done from - Keep provider-specific skills small and explicit. Add a new provider workflow only when it removes real routing ambiguity or covers a provider that does not already offer a first-party agent plugin. - Do not commit machine-local credentials, profiles, `.env` files, cloud state, generated deployment artifacts, or local cache paths. - Use repo-local files, checked-out provider config, provider CLIs, and official provider documentation before making claims about current deployment behavior. +- Keep project worktrees development-only. Release artifacts must come from clean GitHub Actions checkouts; deployment adapters must deploy the manifest digest and must never rebuild source on a host. diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/SKILL.md b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/SKILL.md new file mode 100644 index 00000000..2c8eb05b --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/SKILL.md @@ -0,0 +1,150 @@ +--- +name: dockerized-service-release-deployment-workflow +description: "Create a Dockerized-service release contract with clean GitHub Actions builds, main-anchored tags, immutable digest manifests, published-release deployments, production approval, health checks, and exact-digest rollback." +license: Apache-2.0 +compatibility: Designed for Codex and portable to Hermes as instruction-only guidance for Docker or OCI images, GitHub Actions, GitHub Releases, container registries, and provider-specific deployment adapters. It bundles no provider credential, cloud adapter, or native Hermes runtime. +metadata: + owner: gaelic-ghost + repo: socket + category: cloud-deployment-release +allowed-tools: Read Bash(rg:*) Bash(git:*) Bash(docker:*) Bash(gh:*) Bash(curl:*) Bash(uv:*) Bash(uvx:*) +--- + +# Dockerized Service Release and Deployment Workflow + +## Purpose + +Create or audit a durable release contract for a Dockerized backend or cloud service without treating a developer worktree or a production host as a build machine. + +The default contract is: + +1. Feature worktrees are development-only and may run local tests or image checks. +2. A clean GitHub Actions checkout of a version tag validates the release commit and builds the published OCI image. +3. The tag must resolve to a commit reachable from `origin/main`. +4. The registry digest, tag, commit, image reference, build run, and provenance/SBOM choices are written into a release manifest. +5. The workflow publishes a GitHub Release with that manifest attached: normal SemVer tags become normal releases, and recognized prerelease tags become GitHub prereleases. +6. Publishing the GitHub Release triggers deployment: normal releases target protected `production`; enabled prereleases target `test`. +7. The deployment job invokes a provider-specific adapter with `image@sha256:...` only after the selected environment permits it. +8. The adapter verifies service health and records the prior manifest/digest so rollback deploys a previous exact digest rather than rebuilding source. + +This is a durable building-block change: it removes the ambiguity between a developer build, a CI artifact, and a deployed artifact. A project can later change registries or providers without weakening the tag-to-digest deployment contract. + +## When To Use + +- Use this skill when adding or reviewing Dockerized-service release automation, an OCI registry publication path, GitHub Release deployment triggers, production approval gates, image-digest deployment, release manifests, health checks, or rollback guidance. +- Use it for backend services and cloud workloads regardless of application language when Docker or OCI images are the release artifact. +- Use `server-side-swift:docker-workflow` for Swift Dockerfile, Compose, Linux runtime, and image-entrypoint work. +- Use the official provider plugin or provider documentation for the final deployment adapter. This skill does not create cloud resources, configure a cloud account, or guess a provider command. + +## Source Check + +Inspect the repository's Dockerfile, CI workflows, release process, registry settings, deployment files, health endpoint, provider configuration, and rollback procedures first. Then verify the current platform behavior against these official sources: + +- [GitHub Actions environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) +- [GitHub Actions OIDC](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-cloud-providers) +- [GitHub Releases](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) +- [GitHub Actions release-event triggers](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows) +- [GitHub Actions token-trigger behavior](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) +- [GitHub Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) +- [Docker GitHub Actions image publishing](https://docs.docker.com/build/ci/github-actions/push-multi-registries/) +- [Docker BuildKit attestations](https://docs.docker.com/build/metadata/attestations/) + +Translate those sources into the actual project choices: registry, tag policy, environment policy, provider identity, health URL, and rollback command. + +## Hermes Compatibility + +This is portable instruction-only guidance and is exported through Socket's Hermes skill tap. It does not install a GitHub App, configure registry credentials, provision a cloud account, or bundle a deployment adapter for Hermes or Codex. + +## Required Decisions Before Editing + +Confirm these decisions. Stop rather than silently choosing a production target: + +- registry and image name +- supported target platform or platforms +- tag pattern and the protected integration branch, normally `main` +- repository validation command and image smoke-test command +- GitHub App with only the repository `contents: write` permission; store its client ID as `RELEASE_PUBLISH_APP_CLIENT_ID` and private key as `RELEASE_PUBLISH_APP_PRIVATE_KEY` +- registry-specific `OCI_REGISTRY_USERNAME` repository variable and `OCI_REGISTRY_PASSWORD` secret, unless the adopting repository deliberately narrows the template to GHCR +- GitHub `production` environment reviewers, permitted stable tags, and bypass policy +- whether a `test` environment exists; when it does, enable `ENABLE_PRERELEASE_DEPLOYMENTS=true` as a repository variable and configure its test-tag policy +- cloud account, project, region, workload, and OIDC trust policy for the deployment adapter +- health endpoint, expected response, timeout, and failure diagnostics +- source of the prior successful release manifest for rollback + +## Template Set + +Copy these template assets into the target repository and replace every `{{...}}` placeholder before enabling them: + +- [`release-container.yml.tmpl`](./assets/release-container.yml.tmpl): tag-anchored validation, immutable image publication, manifest creation, and automated normal-release or prerelease publication. +- [`deploy-production.yml.tmpl`](./assets/deploy-production.yml.tmpl): a GitHub Release `published` trigger, protected production or enabled test environment selection, manifest download, exact-digest adapter call, health verification, and failure guidance. +- [`release-manifest.json.tmpl`](./assets/release-manifest.json.tmpl): the release-record schema. +- [`deploy-production-image.sh.tmpl`](./assets/deploy-production-image.sh.tmpl): a deliberately blocked provider-adapter seam. +- [`verify-production-health.sh.tmpl`](./assets/verify-production-health.sh.tmpl): a deliberately blocked health-check seam. + +The shell templates fail closed until a project replaces their placeholders. Do not treat the template itself as a deploy command. + +## Release Workflow + +1. Create feature work in a branch-backed worktree. Use local checks for feedback only; do not publish a release image from that worktree. +2. Open and merge the reviewed change into `main` through the repository's normal CI and review gates. +3. Create an annotated SemVer tag from the reviewed `main` commit and push it. +4. The tag workflow checks out the tag in a clean hosted runner, fetches `origin/main`, and rejects a tag commit that is not reachable from it. +5. Run the repository validation from that clean checkout. +6. Build and push the image once. Capture the registry output digest, then run the container smoke test against that exact published digest. Publish provenance and SBOM attestations where the chosen registry supports them. +7. Accept only `vMAJOR.MINOR.PATCH` stable tags or the recognized prerelease forms `-alpha`, `-beta`, `-rc`, and `-test`, each with an optional dot/hyphen suffix. Reject all other tag forms before publishing. +8. Create `release-manifest.json` from the tag, resolved commit, release kind, immutable image reference, workflow run URL, and attestations. Mint a short-lived GitHub App installation token in the job, then publish a normal GitHub Release for a stable tag or a GitHub prerelease for a recognized prerelease tag. + +The automated publisher needs a GitHub App installation token because a release created with the workflow's `GITHUB_TOKEN` does not trigger the release-published deployment workflow. Configure the App with only the required repository access and mint its token during the job rather than storing a reusable PAT. + +The deployment workflow must listen only for `release: [published]`. It must not deploy directly on a tag push, a push to `main`, or a pull request. + +## Production Deployment and Rollback + +1. The release-published workflow downloads `release-manifest.json` from the exact release event. +2. It validates that the manifest tag matches the release event and that `image` is a fully qualified `name@sha256:digest` reference. +3. A stable release enters the GitHub `production` environment. Configure required reviewers, stable-tag restrictions, and no-administrator-bypass where the repository plan supports them. A recognized prerelease enters `test` only when the repository has explicitly set `ENABLE_PRERELEASE_DEPLOYMENTS=true`; otherwise it publishes without deployment. +4. Authenticate to the provider using short-lived OIDC credentials when supported. Do not put long-lived cloud credentials in repository secrets merely to run this workflow. +5. Pass only the exact digest reference to the provider deployment adapter. The adapter must not run `docker build`, clone source, or reinterpret a mutable tag. +6. Run the repository-defined health check against the deployed release. On failure, preserve deployment logs and stop; do not silently roll forward or rebuild. +7. Roll back by selecting the prior successful GitHub Release manifest and rerunning the same exact-digest deployment path after the required production approval. Do not rebuild an old commit on a host. + +## Security and Supply-Chain Rules + +- Scope workflow permissions minimally. The release workflow needs `contents: read` and `packages: write`; its short-lived GitHub App token has only the separate `contents: write` authority needed to publish the release. The deploy workflow should have read-only contents plus `id-token: write` only when the provider adapter uses OIDC. +- Keep registry write access in the release workflow. Production deployment should normally need only pull/read access plus provider authorization. +- Do not place secrets in Docker build arguments, image layers, caches, release manifests, logs, or committed environment files. +- Treat restored CI caches as untrusted input; do not cache secrets or make a release depend on an unrebuildable cache. +- Use mutable version tags only as discovery aliases. The release manifest and deployment adapter must use the immutable digest. +- Require a real health signal. A TCP connection alone is not a readiness guarantee unless the service explicitly documents it as one. + +## Validation + +Before enabling the workflow: + +1. Validate YAML and shell syntax after template substitution. +2. Run the repository test suite and a local image smoke test for fast feedback. +3. Use a recognized prerelease tag in a repository with `test` enabled to prove that the Actions runner publishes a GitHub prerelease, produces the manifest and registry digest, and deploys its exact digest to `test`. +4. Inspect the manifest against the registry and release tag after automated publication. +5. Confirm a stable tag creates a normal GitHub Release, pauses at `production` approval, and passes `image@sha256:...`, not a tag, to the deployment adapter. +6. Exercise the health-check failure path and a rollback drill using a known prior release digest before treating the workflow as release-ready. + +## Guardrails + +- Do not deploy or modify a cloud account while creating this reusable guidance or template set. +- Do not use a worktree, developer laptop, or production host to build the image that a release deploys. +- Do not publish a GitHub Release from a tag that is not anchored to the protected integration branch. +- Do not publish a release with the workflow `GITHUB_TOKEN` when it must trigger another workflow; use the dedicated release-publisher token. +- Do not deploy an unrecognized, draft, deleted, edited, or merely-created release. Stable releases deploy only to `production`; prereleases deploy only to enabled `test`. +- Do not deploy from `latest`, a branch name, a short SHA tag, or any other mutable image identifier. +- Do not substitute an SSH host copy, `docker compose build`, or remote source checkout for the provider deployment adapter. +- Do not claim rollback readiness until a prior release manifest and exact-digest adapter path have both been verified. + +## Output Shape + +Return: + +1. `Release contract`: branch, tag pattern, registry, image platforms, validation, manifest location, and GitHub Release ownership. +2. `Deployment contract`: provider adapter, OIDC/credential boundary, production environment policy, health signal, and rollback source. +3. `Templates`: copied assets and every required substitution. +4. `Validation`: local, CI, registry, release, environment-approval, health, and rollback evidence. +5. `Risk`: unresolved provider commands, approval policy, plan limitations, health semantics, or rollback gaps. diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl new file mode 100644 index 00000000..ae332d00 --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +deploy_environment=${1:?"Expected the deployment environment, such as test or production."} +image_ref=${2:?"Expected an exact OCI image reference such as registry.example/service@sha256:<64 hex characters>."} + +if [[ ! "$image_ref" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Refusing deployment because '$image_ref' does not include a registry, repository, and immutable OCI sha256 digest." >&2 + exit 1 +fi + +echo "The $deploy_environment deployment adapter template has not been configured for this provider. Replace this file with the provider-owned exact-digest deployment command; do not clone source, run docker build, or deploy a mutable image tag." >&2 +exit 1 diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl new file mode 100644 index 00000000..9dc823e9 --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl @@ -0,0 +1,84 @@ +name: Deploy published release + +on: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + deploy: + if: github.event.release.prerelease == false || vars.ENABLE_PRERELEASE_DEPLOYMENTS == 'true' + runs-on: ubuntu-latest + environment: ${{ github.event.release.prerelease && 'test' || 'production' }} + env: + DEPLOY_ENVIRONMENT: ${{ github.event.release.prerelease && 'test' || 'production' }} + steps: + - name: Check out the release tag for deployment adapters + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Download the published release manifest + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + gh release download "$RELEASE_TAG" --pattern release-manifest.json --dir .release + test -s .release/release-manifest.json + + - name: Validate the release manifest and extract the exact image digest + id: manifest + shell: bash + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + manifest=.release/release-manifest.json + tag=$(jq -r '.releaseTag // empty' "$manifest") + kind=$(jq -r '.releaseKind // empty' "$manifest") + commit=$(jq -r '.commit // empty' "$manifest") + image=$(jq -r '.image // empty' "$manifest") + if [[ "$tag" != "$RELEASE_TAG" ]]; then + echo "Release manifest tag '$tag' does not match published release '$RELEASE_TAG'." >&2 + exit 1 + fi + if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ && "$kind" == stable && "${{ github.event.release.prerelease }}" == false ]]; then + : + elif [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|test)([.-][0-9A-Za-z]+)*$ && "$kind" == prerelease && "${{ github.event.release.prerelease }}" == true ]]; then + : + else + echo "Release tag '$tag', manifest kind '$kind', and GitHub prerelease state do not form an allowed stable or recognized prerelease release." >&2 + exit 1 + fi + if [[ ! "$commit" =~ ^[0-9a-f]{40}$ || "$commit" != "$(git rev-parse HEAD)" ]]; then + echo "Release manifest commit '$commit' does not match the checked-out release tag commit '$(git rev-parse HEAD)'." >&2 + exit 1 + fi + git fetch --no-tags origin main + if ! git merge-base --is-ancestor "$commit" origin/main; then + echo "Release manifest commit '$commit' is not reachable from origin/main; refusing to grant deployment credentials to an unreviewed tag." >&2 + exit 1 + fi + if [[ ! "$image" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Release manifest image must include a registry, repository, and exact OCI sha256 digest; got '$image'." >&2 + exit 1 + fi + echo "image=$image" >> "$GITHUB_OUTPUT" + + - name: Authenticate to the cloud provider with OIDC + shell: bash + run: {{PROVIDER_OIDC_LOGIN_COMMAND}} + + - name: Deploy exactly the published image digest + shell: bash + run: ./scripts/deploy-production-image.sh "$DEPLOY_ENVIRONMENT" '${{ steps.manifest.outputs.image }}' + + - name: Verify the deployed release health + shell: bash + run: ./scripts/verify-production-health.sh "$DEPLOY_ENVIRONMENT" '${{ steps.manifest.outputs.image }}' diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl new file mode 100644 index 00000000..bbb60894 --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl @@ -0,0 +1,128 @@ +name: Release container image + +on: + push: + tags: + - "v*" + +permissions: + contents: read + packages: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Check out the tagged release commit + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Require the tag commit to be reachable from main + shell: bash + run: | + set -euo pipefail + git fetch --no-tags origin main + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then + echo "Release tag $GITHUB_REF_NAME points at $GITHUB_SHA, which is not reachable from origin/main. Create release tags from reviewed main only." >&2 + exit 1 + fi + + - name: Classify the release tag + id: release_kind + shell: bash + run: | + set -euo pipefail + tag=$GITHUB_REF_NAME + if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "kind=stable" >> "$GITHUB_OUTPUT" + elif [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|test)([.-][0-9A-Za-z]+)*$ ]]; then + echo "kind=prerelease" >> "$GITHUB_OUTPUT" + else + echo "Release tag '$tag' is not stable SemVer or a recognized alpha, beta, rc, or test prerelease. Refusing publication." >&2 + exit 1 + fi + + - name: Run repository validation + shell: bash + run: {{REPOSITORY_VALIDATION_COMMAND}} + + - name: Log in to the OCI registry + uses: docker/login-action@v4 + with: + registry: {{OCI_REGISTRY}} + username: ${{ vars.OCI_REGISTRY_USERNAME }} + password: ${{ secrets.OCI_REGISTRY_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and push the immutable release image + id: build + uses: docker/build-push-action@v7 + with: + context: . + file: {{DOCKERFILE_PATH}} + platforms: {{OCI_PLATFORMS}} + push: true + provenance: mode=max + sbom: true + tags: | + {{OCI_REGISTRY}}/{{IMAGE_NAME}}:${{ github.ref_name }} + {{OCI_REGISTRY}}/{{IMAGE_NAME}}:sha-${{ github.sha }} + + - name: Smoke-test the exact published image digest + shell: bash + env: + RELEASE_IMAGE: {{OCI_REGISTRY}}/{{IMAGE_NAME}}@${{ steps.build.outputs.digest }} + run: {{CONTAINER_DIGEST_SMOKE_TEST_COMMAND}} + + - name: Write the release manifest + shell: bash + env: + IMAGE_DIGEST: ${{ steps.build.outputs.digest }} + run: | + set -euo pipefail + if [[ ! "$IMAGE_DIGEST" =~ ^sha256: ]]; then + echo "Registry publication did not return an immutable sha256 image digest; refusing to create a release manifest." >&2 + exit 1 + fi + jq -n \ + --arg tag "$GITHUB_REF_NAME" \ + --arg release_kind "${{ steps.release_kind.outputs.kind }}" \ + --arg commit "$GITHUB_SHA" \ + --arg image "{{OCI_REGISTRY}}/{{IMAGE_NAME}}@$IMAGE_DIGEST" \ + --arg image_tag "{{OCI_REGISTRY}}/{{IMAGE_NAME}}:$GITHUB_REF_NAME" \ + --arg source "https://github.com/$GITHUB_REPOSITORY" \ + --arg build_run "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + --arg healthcheck "{{DOCUMENTED_HEALTHCHECK_NAME}}" \ + --arg platforms "{{OCI_PLATFORMS}}" \ + --arg provenance "BuildKit provenance: mode=max" \ + --arg sbom "BuildKit SBOM: enabled" \ + '{schemaVersion: 1, releaseTag: $tag, releaseKind: $release_kind, commit: $commit, image: $image, imageTag: $image_tag, platforms: ($platforms | split(",")), sourceRepository: $source, buildRun: $build_run, provenance: $provenance, sbom: $sbom, healthCheck: $healthcheck}' \ + > release-manifest.json + + - name: Mint a short-lived GitHub App token for release publication + id: release_token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.RELEASE_PUBLISH_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_PUBLISH_APP_PRIVATE_KEY }} + permission-contents: write + + - name: Publish the GitHub Release with the manifest + shell: bash + env: + GH_TOKEN: ${{ steps.release_token.outputs.token }} + RELEASE_KIND: ${{ steps.release_kind.outputs.kind }} + run: | + set -euo pipefail + if [[ -z "$GH_TOKEN" ]]; then + echo "The GitHub App installation token is required to publish a release that triggers the deployment workflow. Configure RELEASE_PUBLISH_APP_CLIENT_ID and RELEASE_PUBLISH_APP_PRIVATE_KEY before tagging." >&2 + exit 1 + fi + args=("$GITHUB_REF_NAME" release-manifest.json --target "$GITHUB_SHA" --title "$GITHUB_REF_NAME" --generate-notes) + if [[ "$RELEASE_KIND" == prerelease ]]; then + args+=(--prerelease) + fi + gh release create "${args[@]}" diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl new file mode 100644 index 00000000..06325b7b --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "releaseTag": "{{RELEASE_TAG}}", + "releaseKind": "{{stable|prerelease}}", + "commit": "{{FULL_GIT_COMMIT_SHA}}", + "image": "{{REGISTRY}}/{{IMAGE_NAME}}@{{IMAGE_DIGEST}}", + "imageTag": "{{REGISTRY}}/{{IMAGE_NAME}}:{{RELEASE_TAG}}", + "platforms": ["{{OCI_PLATFORM}}"], + "sourceRepository": "{{GITHUB_REPOSITORY_URL}}", + "buildRun": "{{GITHUB_ACTIONS_RUN_URL}}", + "provenance": "{{PROVENANCE_REFERENCE_OR_POLICY}}", + "sbom": "{{SBOM_REFERENCE_OR_POLICY}}", + "healthCheck": "{{DOCUMENTED_HEALTHCHECK_NAME}}" +} diff --git a/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl new file mode 100644 index 00000000..c77e171a --- /dev/null +++ b/plugins/cloud-deployment-skills/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +deploy_environment=${1:?"Expected the deployment environment, such as test or production."} +image_ref=${2:?"Expected the exact OCI image digest deployed by the release workflow."} + +if [[ ! "$image_ref" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Refusing health verification because '$image_ref' does not include a registry, repository, and immutable OCI sha256 digest." >&2 + exit 1 +fi + +echo "The $deploy_environment health-check template has not been configured. Replace it with a bounded check of the documented readiness endpoint, expected response, timeout, and diagnostic-log location." >&2 +exit 1 diff --git a/plugins/cloud-inference-skills/.codex-plugin/plugin.json b/plugins/cloud-inference-skills/.codex-plugin/plugin.json index 89e58e80..ee5de962 100644 --- a/plugins/cloud-inference-skills/.codex-plugin/plugin.json +++ b/plugins/cloud-inference-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "cloud-inference-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Cloud AI inference workflow skills for routing model serving, training, conversion, and GPU infrastructure work across Runpod, Hugging Face, AWS, Vast.ai, CoreWeave, and similar providers.", "author": { "name": "Gale", diff --git a/plugins/cybersecurity-skills/.codex-plugin/plugin.json b/plugins/cybersecurity-skills/.codex-plugin/plugin.json index 11585c02..6593e935 100644 --- a/plugins/cybersecurity-skills/.codex-plugin/plugin.json +++ b/plugins/cybersecurity-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "cybersecurity-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Defensive cybersecurity, isolated Linux and macOS analysis labs, suspicious-content and malware analysis, macOS defense, vulnerability testing, pentesting, and incident response workflows.", "skills": "./skills/", "author": { diff --git a/plugins/dotnet-skills/.codex-plugin/plugin.json b/plugins/dotnet-skills/.codex-plugin/plugin.json index 6a1acec7..b955be53 100644 --- a/plugins/dotnet-skills/.codex-plugin/plugin.json +++ b/plugins/dotnet-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "dotnet-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for choosing, bootstrapping, building, testing, packaging, diagnosing, and maintaining .NET projects, including F# web applications, with F# and C# as equal first-party languages.", "author": { "name": "Gale", diff --git a/plugins/game-dev-skills/.codex-plugin/plugin.json b/plugins/game-dev-skills/.codex-plugin/plugin.json index 6569b642..e88e10e3 100644 --- a/plugins/game-dev-skills/.codex-plugin/plugin.json +++ b/plugins/game-dev-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "game-dev-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Apple platform game development workflow skills for native Metal rendering, Game Porting Toolkit routing, MetalFX, GPU asset streaming, neural rendering, frameworks, input, haptics, and profiling.", "author": { "name": "Gale", diff --git a/plugins/messaging-collaboration-skills/.codex-plugin/plugin.json b/plugins/messaging-collaboration-skills/.codex-plugin/plugin.json index b9fb4e5a..f9bfc77b 100644 --- a/plugins/messaging-collaboration-skills/.codex-plugin/plugin.json +++ b/plugins/messaging-collaboration-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "messaging-collaboration-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex workflows for chat apps, bots, collaboration, iMessage, Apple notifications and Push to Talk, VoIP, and default communication-app planning.", "author": { "name": "Gale", diff --git a/plugins/model-lab-skills/.codex-plugin/plugin.json b/plugins/model-lab-skills/.codex-plugin/plugin.json index 74ffe498..c8728c49 100644 --- a/plugins/model-lab-skills/.codex-plugin/plugin.json +++ b/plugins/model-lab-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "model-lab-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Reproducible model training, evaluation, intervention, and runtime research workflows.", "author": { "name": "Gale" diff --git a/plugins/network-protocol-skills/.codex-plugin/plugin.json b/plugins/network-protocol-skills/.codex-plugin/plugin.json index 327c0238..23adb6c5 100644 --- a/plugins/network-protocol-skills/.codex-plugin/plugin.json +++ b/plugins/network-protocol-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "network-protocol-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for choosing, planning, implementing, and diagnosing modern application transports and real-time networking protocols, including QUIC, HTTP/3, WebRTC, Media over QUIC, WebTransport-adjacent handoffs, protocol maturity checks, and stack-specific implementation routing.", "author": { "name": "Gale", diff --git a/plugins/productivity-skills/.codex-plugin/plugin.json b/plugins/productivity-skills/.codex-plugin/plugin.json index e587a452..643a3665 100644 --- a/plugins/productivity-skills/.codex-plugin/plugin.json +++ b/plugins/productivity-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "productivity-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Broadly useful productivity workflows for Codex.", "author": { "name": "Gale", diff --git a/plugins/productivity-skills/pyproject.toml b/plugins/productivity-skills/pyproject.toml index c2c54a03..5a10f950 100644 --- a/plugins/productivity-skills/pyproject.toml +++ b/plugins/productivity-skills/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "productivity-skills-maintenance" -version = "9.26.1" +version = "9.27.0" description = "Maintainer-only Python tooling baseline for productivity-skills." requires-python = ">=3.11" dependencies = [] diff --git a/plugins/productivity-skills/uv.lock b/plugins/productivity-skills/uv.lock index 87517479..1f21b029 100644 --- a/plugins/productivity-skills/uv.lock +++ b/plugins/productivity-skills/uv.lock @@ -228,7 +228,7 @@ wheels = [ [[package]] name = "productivity-skills-maintenance" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies] diff --git a/plugins/python-skills/.codex-plugin/plugin.json b/plugins/python-skills/.codex-plugin/plugin.json index 0ce93ccd..9dc9f03f 100644 --- a/plugins/python-skills/.codex-plugin/plugin.json +++ b/plugins/python-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "python-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Bundled Python-focused Codex skills for uv bootstrapping, project implementation, diagnostics, packaging, tooling, CI, upgrades, FastAPI service maintenance, FastMCP service maintenance, and testing workflows.", "author": { "name": "Gale", diff --git a/plugins/python-skills/pyproject.toml b/plugins/python-skills/pyproject.toml index 824b60b8..e0b9c1f5 100644 --- a/plugins/python-skills/pyproject.toml +++ b/plugins/python-skills/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-skills-maintainer" -version = "9.26.1" +version = "9.27.0" description = "Maintainer tooling for the python-skills repository" requires-python = ">=3.11" dependencies = [] diff --git a/plugins/python-skills/uv.lock b/plugins/python-skills/uv.lock index f1a9728c..f1872b3e 100644 --- a/plugins/python-skills/uv.lock +++ b/plugins/python-skills/uv.lock @@ -251,7 +251,7 @@ wheels = [ [[package]] name = "python-skills-maintainer" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies] diff --git a/plugins/reverse-engineering-skills/.codex-plugin/plugin.json b/plugins/reverse-engineering-skills/.codex-plugin/plugin.json index 02e9fc57..63a54747 100644 --- a/plugins/reverse-engineering-skills/.codex-plugin/plugin.json +++ b/plugins/reverse-engineering-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "reverse-engineering-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Workflow skills for reverse engineering, decompilation, disassembly, symbols, artifact analysis, and exact-build macOS security-control research.", "skills": "./skills/", "author": { diff --git a/plugins/rust-skills/.codex-plugin/plugin.json b/plugins/rust-skills/.codex-plugin/plugin.json index e1a1df3c..2f8f6b2a 100644 --- a/plugins/rust-skills/.codex-plugin/plugin.json +++ b/plugins/rust-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "rust-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Rust, Cargo, rustup, crate, workspace, CLI, library, package, CI, testing, linting, and formatting workflow skills.", "skills": "./skills/", "author": { diff --git a/plugins/server-side-jvm/.codex-plugin/plugin.json b/plugins/server-side-jvm/.codex-plugin/plugin.json index 10aed411..245c797c 100644 --- a/plugins/server-side-jvm/.codex-plugin/plugin.json +++ b/plugins/server-side-jvm/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "server-side-jvm", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for choosing, building, testing, and maintaining server-side JVM backend projects with Java and Scala as equal first-party languages and future Clojure support planned.", "author": { "name": "Gale", diff --git a/plugins/server-side-swift/.codex-plugin/plugin.json b/plugins/server-side-swift/.codex-plugin/plugin.json index fb049bb8..00a2eaf2 100644 --- a/plugins/server-side-swift/.codex-plugin/plugin.json +++ b/plugins/server-side-swift/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "server-side-swift", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for bootstrapping, syncing, building, running, containerizing, deploying, and maintaining server-side Swift services, including Vapor, Hummingbird, hb, persistence, Swift OpenAPI, RPC-fit decisions, SwiftNIO, observability, auth, app sync, Docker, Apple Containerization, Fly.io, and SwiftPM-first workflows.", "author": { "name": "Gale", diff --git a/plugins/server-side-swift/skills/docker-workflow/SKILL.md b/plugins/server-side-swift/skills/docker-workflow/SKILL.md index 88c4b7b3..e1e2689c 100644 --- a/plugins/server-side-swift/skills/docker-workflow/SKILL.md +++ b/plugins/server-side-swift/skills/docker-workflow/SKILL.md @@ -44,6 +44,8 @@ Use Vapor, Hummingbird, SwiftPM, or Linux distribution documentation when the co Use Fly.io documentation and `fly-io-deployment-workflow` when Docker image work is being prepared for Fly-specific `fly.toml`, `fly launch`, `fly deploy`, Fly secrets, Fly process groups, Fly health checks, or Fly Postgres attachment. +Use `cloud-deployment-skills:dockerized-service-release-deployment-workflow` when a production image needs a reusable GitHub Actions release path, immutable digest manifest, GitHub Release deployment trigger, production approval, exact-digest deploy, health verification, or rollback contract. This skill remains the owner of the Dockerfile and image-runtime shape. + ## Planning Workflow 1. Inspect project shape: diff --git a/plugins/spotify/.codex-plugin/plugin.json b/plugins/spotify/.codex-plugin/plugin.json index 2cf2103d..c9bc1618 100644 --- a/plugins/spotify/.codex-plugin/plugin.json +++ b/plugins/spotify/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spotify", - "version": "9.26.1", + "version": "9.27.0", "description": "Placeholder plugin repository for future Spotify-focused Codex workflows.", "author": { "name": "Gale", diff --git a/plugins/swift-lang/.codex-plugin/plugin.json b/plugins/swift-lang/.codex-plugin/plugin.json index 4aca7288..9fad54f8 100644 --- a/plugins/swift-lang/.codex-plugin/plugin.json +++ b/plugins/swift-lang/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "swift-lang", - "version": "9.26.1", + "version": "9.27.0", "description": "Shared Swift language and tooling skills for API style, errors, functional pipelines, formatting, source organization, SwiftSyntax, compiler inspection, SourceKit, indexing, SourceKit-LSP, and modernization.", "skills": "./skills/", "author": { diff --git a/plugins/swiftasb-skills/.codex-plugin/plugin.json b/plugins/swiftasb-skills/.codex-plugin/plugin.json index 3d1bf914..ca2aa07d 100644 --- a/plugins/swiftasb-skills/.codex-plugin/plugin.json +++ b/plugins/swiftasb-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "swiftasb-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for explaining SwiftASB and building SwiftUI, AppKit, and Swift package integrations on top of it.", "author": { "name": "Gale", diff --git a/plugins/things-app/.codex-plugin/plugin.json b/plugins/things-app/.codex-plugin/plugin.json index baa1b65b..cb558cfa 100644 --- a/plugins/things-app/.codex-plugin/plugin.json +++ b/plugins/things-app/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "things-app", - "version": "9.26.1", + "version": "9.27.0", "description": "Things.app skills and a bundled local MCP server for reminders, planning digests, and structured task workflows.", "author": { "name": "Gale", diff --git a/plugins/things-app/mcp/pyproject.toml b/plugins/things-app/mcp/pyproject.toml index 73a70575..7fb3accc 100644 --- a/plugins/things-app/mcp/pyproject.toml +++ b/plugins/things-app/mcp/pyproject.toml @@ -7,7 +7,7 @@ packages = ["app"] [project] name = "things-mcp" -version = "9.26.1" +version = "9.27.0" requires-python = ">=3.13" dependencies = [ "fastmcp>=3.0.2", diff --git a/plugins/things-app/mcp/uv.lock b/plugins/things-app/mcp/uv.lock index 0e0c0ec7..df6ec6f5 100644 --- a/plugins/things-app/mcp/uv.lock +++ b/plugins/things-app/mcp/uv.lock @@ -1244,7 +1244,7 @@ wheels = [ [[package]] name = "things-mcp" -version = "9.26.1" +version = "9.27.0" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/plugins/things-app/pyproject.toml b/plugins/things-app/pyproject.toml index bf77d58f..34e2e422 100644 --- a/plugins/things-app/pyproject.toml +++ b/plugins/things-app/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "things-app-maintenance" -version = "9.26.1" +version = "9.27.0" description = "Maintainer-only Python tooling baseline for things-app skills and plugin packaging." requires-python = ">=3.11" dependencies = [] diff --git a/plugins/things-app/uv.lock b/plugins/things-app/uv.lock index 93fc0921..0e7c9ebe 100644 --- a/plugins/things-app/uv.lock +++ b/plugins/things-app/uv.lock @@ -120,7 +120,7 @@ wheels = [ [[package]] name = "things-app-maintenance" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies] diff --git a/plugins/web-dev-skills/.codex-plugin/plugin.json b/plugins/web-dev-skills/.codex-plugin/plugin.json index c5d32249..95d74dd5 100644 --- a/plugins/web-dev-skills/.codex-plugin/plugin.json +++ b/plugins/web-dev-skills/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "web-dev-skills", - "version": "9.26.1", + "version": "9.27.0", "description": "Codex skills for focused web and Expo native-boundary workflows.", "author": { "name": "Gale", diff --git a/pyproject.toml b/pyproject.toml index 57b75077..f2f64a30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "socket-maintenance" -version = "9.26.1" +version = "9.27.0" description = "Root uv tooling baseline for the socket superproject." requires-python = ">=3.11" dependencies = [] diff --git a/scripts/export_hermes_skills.py b/scripts/export_hermes_skills.py index e959282a..3cfdef77 100644 --- a/scripts/export_hermes_skills.py +++ b/scripts/export_hermes_skills.py @@ -150,7 +150,10 @@ "python-testing-workflow", ) JVM_SKILLS = ("build-jvm-agent-service",) -CLOUD_DEPLOYMENT_SKILLS = ("cloud-deployment-routing-workflow",) +CLOUD_DEPLOYMENT_SKILLS = ( + "cloud-deployment-routing-workflow", + "dockerized-service-release-deployment-workflow", +) EXPORTED_SKILLS = ( AGENT_PORTABILITY_SKILLS + MESSAGING_SKILLS diff --git a/skills.sh.json b/skills.sh.json index 252138b9..14287f26 100644 --- a/skills.sh.json +++ b/skills.sh.json @@ -127,7 +127,8 @@ { "title": "Cloud Deployment Skills", "skills": [ - "cloud-deployment-routing-workflow" + "cloud-deployment-routing-workflow", + "dockerized-service-release-deployment-workflow" ] }, { diff --git a/skills/dockerized-service-release-deployment-workflow/SKILL.md b/skills/dockerized-service-release-deployment-workflow/SKILL.md new file mode 100644 index 00000000..2c8eb05b --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/SKILL.md @@ -0,0 +1,150 @@ +--- +name: dockerized-service-release-deployment-workflow +description: "Create a Dockerized-service release contract with clean GitHub Actions builds, main-anchored tags, immutable digest manifests, published-release deployments, production approval, health checks, and exact-digest rollback." +license: Apache-2.0 +compatibility: Designed for Codex and portable to Hermes as instruction-only guidance for Docker or OCI images, GitHub Actions, GitHub Releases, container registries, and provider-specific deployment adapters. It bundles no provider credential, cloud adapter, or native Hermes runtime. +metadata: + owner: gaelic-ghost + repo: socket + category: cloud-deployment-release +allowed-tools: Read Bash(rg:*) Bash(git:*) Bash(docker:*) Bash(gh:*) Bash(curl:*) Bash(uv:*) Bash(uvx:*) +--- + +# Dockerized Service Release and Deployment Workflow + +## Purpose + +Create or audit a durable release contract for a Dockerized backend or cloud service without treating a developer worktree or a production host as a build machine. + +The default contract is: + +1. Feature worktrees are development-only and may run local tests or image checks. +2. A clean GitHub Actions checkout of a version tag validates the release commit and builds the published OCI image. +3. The tag must resolve to a commit reachable from `origin/main`. +4. The registry digest, tag, commit, image reference, build run, and provenance/SBOM choices are written into a release manifest. +5. The workflow publishes a GitHub Release with that manifest attached: normal SemVer tags become normal releases, and recognized prerelease tags become GitHub prereleases. +6. Publishing the GitHub Release triggers deployment: normal releases target protected `production`; enabled prereleases target `test`. +7. The deployment job invokes a provider-specific adapter with `image@sha256:...` only after the selected environment permits it. +8. The adapter verifies service health and records the prior manifest/digest so rollback deploys a previous exact digest rather than rebuilding source. + +This is a durable building-block change: it removes the ambiguity between a developer build, a CI artifact, and a deployed artifact. A project can later change registries or providers without weakening the tag-to-digest deployment contract. + +## When To Use + +- Use this skill when adding or reviewing Dockerized-service release automation, an OCI registry publication path, GitHub Release deployment triggers, production approval gates, image-digest deployment, release manifests, health checks, or rollback guidance. +- Use it for backend services and cloud workloads regardless of application language when Docker or OCI images are the release artifact. +- Use `server-side-swift:docker-workflow` for Swift Dockerfile, Compose, Linux runtime, and image-entrypoint work. +- Use the official provider plugin or provider documentation for the final deployment adapter. This skill does not create cloud resources, configure a cloud account, or guess a provider command. + +## Source Check + +Inspect the repository's Dockerfile, CI workflows, release process, registry settings, deployment files, health endpoint, provider configuration, and rollback procedures first. Then verify the current platform behavior against these official sources: + +- [GitHub Actions environments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) +- [GitHub Actions OIDC](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-cloud-providers) +- [GitHub Releases](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) +- [GitHub Actions release-event triggers](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows) +- [GitHub Actions token-trigger behavior](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) +- [GitHub Container registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) +- [Docker GitHub Actions image publishing](https://docs.docker.com/build/ci/github-actions/push-multi-registries/) +- [Docker BuildKit attestations](https://docs.docker.com/build/metadata/attestations/) + +Translate those sources into the actual project choices: registry, tag policy, environment policy, provider identity, health URL, and rollback command. + +## Hermes Compatibility + +This is portable instruction-only guidance and is exported through Socket's Hermes skill tap. It does not install a GitHub App, configure registry credentials, provision a cloud account, or bundle a deployment adapter for Hermes or Codex. + +## Required Decisions Before Editing + +Confirm these decisions. Stop rather than silently choosing a production target: + +- registry and image name +- supported target platform or platforms +- tag pattern and the protected integration branch, normally `main` +- repository validation command and image smoke-test command +- GitHub App with only the repository `contents: write` permission; store its client ID as `RELEASE_PUBLISH_APP_CLIENT_ID` and private key as `RELEASE_PUBLISH_APP_PRIVATE_KEY` +- registry-specific `OCI_REGISTRY_USERNAME` repository variable and `OCI_REGISTRY_PASSWORD` secret, unless the adopting repository deliberately narrows the template to GHCR +- GitHub `production` environment reviewers, permitted stable tags, and bypass policy +- whether a `test` environment exists; when it does, enable `ENABLE_PRERELEASE_DEPLOYMENTS=true` as a repository variable and configure its test-tag policy +- cloud account, project, region, workload, and OIDC trust policy for the deployment adapter +- health endpoint, expected response, timeout, and failure diagnostics +- source of the prior successful release manifest for rollback + +## Template Set + +Copy these template assets into the target repository and replace every `{{...}}` placeholder before enabling them: + +- [`release-container.yml.tmpl`](./assets/release-container.yml.tmpl): tag-anchored validation, immutable image publication, manifest creation, and automated normal-release or prerelease publication. +- [`deploy-production.yml.tmpl`](./assets/deploy-production.yml.tmpl): a GitHub Release `published` trigger, protected production or enabled test environment selection, manifest download, exact-digest adapter call, health verification, and failure guidance. +- [`release-manifest.json.tmpl`](./assets/release-manifest.json.tmpl): the release-record schema. +- [`deploy-production-image.sh.tmpl`](./assets/deploy-production-image.sh.tmpl): a deliberately blocked provider-adapter seam. +- [`verify-production-health.sh.tmpl`](./assets/verify-production-health.sh.tmpl): a deliberately blocked health-check seam. + +The shell templates fail closed until a project replaces their placeholders. Do not treat the template itself as a deploy command. + +## Release Workflow + +1. Create feature work in a branch-backed worktree. Use local checks for feedback only; do not publish a release image from that worktree. +2. Open and merge the reviewed change into `main` through the repository's normal CI and review gates. +3. Create an annotated SemVer tag from the reviewed `main` commit and push it. +4. The tag workflow checks out the tag in a clean hosted runner, fetches `origin/main`, and rejects a tag commit that is not reachable from it. +5. Run the repository validation from that clean checkout. +6. Build and push the image once. Capture the registry output digest, then run the container smoke test against that exact published digest. Publish provenance and SBOM attestations where the chosen registry supports them. +7. Accept only `vMAJOR.MINOR.PATCH` stable tags or the recognized prerelease forms `-alpha`, `-beta`, `-rc`, and `-test`, each with an optional dot/hyphen suffix. Reject all other tag forms before publishing. +8. Create `release-manifest.json` from the tag, resolved commit, release kind, immutable image reference, workflow run URL, and attestations. Mint a short-lived GitHub App installation token in the job, then publish a normal GitHub Release for a stable tag or a GitHub prerelease for a recognized prerelease tag. + +The automated publisher needs a GitHub App installation token because a release created with the workflow's `GITHUB_TOKEN` does not trigger the release-published deployment workflow. Configure the App with only the required repository access and mint its token during the job rather than storing a reusable PAT. + +The deployment workflow must listen only for `release: [published]`. It must not deploy directly on a tag push, a push to `main`, or a pull request. + +## Production Deployment and Rollback + +1. The release-published workflow downloads `release-manifest.json` from the exact release event. +2. It validates that the manifest tag matches the release event and that `image` is a fully qualified `name@sha256:digest` reference. +3. A stable release enters the GitHub `production` environment. Configure required reviewers, stable-tag restrictions, and no-administrator-bypass where the repository plan supports them. A recognized prerelease enters `test` only when the repository has explicitly set `ENABLE_PRERELEASE_DEPLOYMENTS=true`; otherwise it publishes without deployment. +4. Authenticate to the provider using short-lived OIDC credentials when supported. Do not put long-lived cloud credentials in repository secrets merely to run this workflow. +5. Pass only the exact digest reference to the provider deployment adapter. The adapter must not run `docker build`, clone source, or reinterpret a mutable tag. +6. Run the repository-defined health check against the deployed release. On failure, preserve deployment logs and stop; do not silently roll forward or rebuild. +7. Roll back by selecting the prior successful GitHub Release manifest and rerunning the same exact-digest deployment path after the required production approval. Do not rebuild an old commit on a host. + +## Security and Supply-Chain Rules + +- Scope workflow permissions minimally. The release workflow needs `contents: read` and `packages: write`; its short-lived GitHub App token has only the separate `contents: write` authority needed to publish the release. The deploy workflow should have read-only contents plus `id-token: write` only when the provider adapter uses OIDC. +- Keep registry write access in the release workflow. Production deployment should normally need only pull/read access plus provider authorization. +- Do not place secrets in Docker build arguments, image layers, caches, release manifests, logs, or committed environment files. +- Treat restored CI caches as untrusted input; do not cache secrets or make a release depend on an unrebuildable cache. +- Use mutable version tags only as discovery aliases. The release manifest and deployment adapter must use the immutable digest. +- Require a real health signal. A TCP connection alone is not a readiness guarantee unless the service explicitly documents it as one. + +## Validation + +Before enabling the workflow: + +1. Validate YAML and shell syntax after template substitution. +2. Run the repository test suite and a local image smoke test for fast feedback. +3. Use a recognized prerelease tag in a repository with `test` enabled to prove that the Actions runner publishes a GitHub prerelease, produces the manifest and registry digest, and deploys its exact digest to `test`. +4. Inspect the manifest against the registry and release tag after automated publication. +5. Confirm a stable tag creates a normal GitHub Release, pauses at `production` approval, and passes `image@sha256:...`, not a tag, to the deployment adapter. +6. Exercise the health-check failure path and a rollback drill using a known prior release digest before treating the workflow as release-ready. + +## Guardrails + +- Do not deploy or modify a cloud account while creating this reusable guidance or template set. +- Do not use a worktree, developer laptop, or production host to build the image that a release deploys. +- Do not publish a GitHub Release from a tag that is not anchored to the protected integration branch. +- Do not publish a release with the workflow `GITHUB_TOKEN` when it must trigger another workflow; use the dedicated release-publisher token. +- Do not deploy an unrecognized, draft, deleted, edited, or merely-created release. Stable releases deploy only to `production`; prereleases deploy only to enabled `test`. +- Do not deploy from `latest`, a branch name, a short SHA tag, or any other mutable image identifier. +- Do not substitute an SSH host copy, `docker compose build`, or remote source checkout for the provider deployment adapter. +- Do not claim rollback readiness until a prior release manifest and exact-digest adapter path have both been verified. + +## Output Shape + +Return: + +1. `Release contract`: branch, tag pattern, registry, image platforms, validation, manifest location, and GitHub Release ownership. +2. `Deployment contract`: provider adapter, OIDC/credential boundary, production environment policy, health signal, and rollback source. +3. `Templates`: copied assets and every required substitution. +4. `Validation`: local, CI, registry, release, environment-approval, health, and rollback evidence. +5. `Risk`: unresolved provider commands, approval policy, plan limitations, health semantics, or rollback gaps. diff --git a/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl b/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl new file mode 100644 index 00000000..ae332d00 --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/assets/deploy-production-image.sh.tmpl @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +deploy_environment=${1:?"Expected the deployment environment, such as test or production."} +image_ref=${2:?"Expected an exact OCI image reference such as registry.example/service@sha256:<64 hex characters>."} + +if [[ ! "$image_ref" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Refusing deployment because '$image_ref' does not include a registry, repository, and immutable OCI sha256 digest." >&2 + exit 1 +fi + +echo "The $deploy_environment deployment adapter template has not been configured for this provider. Replace this file with the provider-owned exact-digest deployment command; do not clone source, run docker build, or deploy a mutable image tag." >&2 +exit 1 diff --git a/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl b/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl new file mode 100644 index 00000000..9dc823e9 --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/assets/deploy-production.yml.tmpl @@ -0,0 +1,84 @@ +name: Deploy published release + +on: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + deploy: + if: github.event.release.prerelease == false || vars.ENABLE_PRERELEASE_DEPLOYMENTS == 'true' + runs-on: ubuntu-latest + environment: ${{ github.event.release.prerelease && 'test' || 'production' }} + env: + DEPLOY_ENVIRONMENT: ${{ github.event.release.prerelease && 'test' || 'production' }} + steps: + - name: Check out the release tag for deployment adapters + uses: actions/checkout@v6.0.2 + with: + ref: ${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Download the published release manifest + shell: bash + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + gh release download "$RELEASE_TAG" --pattern release-manifest.json --dir .release + test -s .release/release-manifest.json + + - name: Validate the release manifest and extract the exact image digest + id: manifest + shell: bash + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + set -euo pipefail + manifest=.release/release-manifest.json + tag=$(jq -r '.releaseTag // empty' "$manifest") + kind=$(jq -r '.releaseKind // empty' "$manifest") + commit=$(jq -r '.commit // empty' "$manifest") + image=$(jq -r '.image // empty' "$manifest") + if [[ "$tag" != "$RELEASE_TAG" ]]; then + echo "Release manifest tag '$tag' does not match published release '$RELEASE_TAG'." >&2 + exit 1 + fi + if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ && "$kind" == stable && "${{ github.event.release.prerelease }}" == false ]]; then + : + elif [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|test)([.-][0-9A-Za-z]+)*$ && "$kind" == prerelease && "${{ github.event.release.prerelease }}" == true ]]; then + : + else + echo "Release tag '$tag', manifest kind '$kind', and GitHub prerelease state do not form an allowed stable or recognized prerelease release." >&2 + exit 1 + fi + if [[ ! "$commit" =~ ^[0-9a-f]{40}$ || "$commit" != "$(git rev-parse HEAD)" ]]; then + echo "Release manifest commit '$commit' does not match the checked-out release tag commit '$(git rev-parse HEAD)'." >&2 + exit 1 + fi + git fetch --no-tags origin main + if ! git merge-base --is-ancestor "$commit" origin/main; then + echo "Release manifest commit '$commit' is not reachable from origin/main; refusing to grant deployment credentials to an unreviewed tag." >&2 + exit 1 + fi + if [[ ! "$image" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Release manifest image must include a registry, repository, and exact OCI sha256 digest; got '$image'." >&2 + exit 1 + fi + echo "image=$image" >> "$GITHUB_OUTPUT" + + - name: Authenticate to the cloud provider with OIDC + shell: bash + run: {{PROVIDER_OIDC_LOGIN_COMMAND}} + + - name: Deploy exactly the published image digest + shell: bash + run: ./scripts/deploy-production-image.sh "$DEPLOY_ENVIRONMENT" '${{ steps.manifest.outputs.image }}' + + - name: Verify the deployed release health + shell: bash + run: ./scripts/verify-production-health.sh "$DEPLOY_ENVIRONMENT" '${{ steps.manifest.outputs.image }}' diff --git a/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl b/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl new file mode 100644 index 00000000..bbb60894 --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/assets/release-container.yml.tmpl @@ -0,0 +1,128 @@ +name: Release container image + +on: + push: + tags: + - "v*" + +permissions: + contents: read + packages: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Check out the tagged release commit + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 + + - name: Require the tag commit to be reachable from main + shell: bash + run: | + set -euo pipefail + git fetch --no-tags origin main + if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then + echo "Release tag $GITHUB_REF_NAME points at $GITHUB_SHA, which is not reachable from origin/main. Create release tags from reviewed main only." >&2 + exit 1 + fi + + - name: Classify the release tag + id: release_kind + shell: bash + run: | + set -euo pipefail + tag=$GITHUB_REF_NAME + if [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "kind=stable" >> "$GITHUB_OUTPUT" + elif [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-(alpha|beta|rc|test)([.-][0-9A-Za-z]+)*$ ]]; then + echo "kind=prerelease" >> "$GITHUB_OUTPUT" + else + echo "Release tag '$tag' is not stable SemVer or a recognized alpha, beta, rc, or test prerelease. Refusing publication." >&2 + exit 1 + fi + + - name: Run repository validation + shell: bash + run: {{REPOSITORY_VALIDATION_COMMAND}} + + - name: Log in to the OCI registry + uses: docker/login-action@v4 + with: + registry: {{OCI_REGISTRY}} + username: ${{ vars.OCI_REGISTRY_USERNAME }} + password: ${{ secrets.OCI_REGISTRY_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Build and push the immutable release image + id: build + uses: docker/build-push-action@v7 + with: + context: . + file: {{DOCKERFILE_PATH}} + platforms: {{OCI_PLATFORMS}} + push: true + provenance: mode=max + sbom: true + tags: | + {{OCI_REGISTRY}}/{{IMAGE_NAME}}:${{ github.ref_name }} + {{OCI_REGISTRY}}/{{IMAGE_NAME}}:sha-${{ github.sha }} + + - name: Smoke-test the exact published image digest + shell: bash + env: + RELEASE_IMAGE: {{OCI_REGISTRY}}/{{IMAGE_NAME}}@${{ steps.build.outputs.digest }} + run: {{CONTAINER_DIGEST_SMOKE_TEST_COMMAND}} + + - name: Write the release manifest + shell: bash + env: + IMAGE_DIGEST: ${{ steps.build.outputs.digest }} + run: | + set -euo pipefail + if [[ ! "$IMAGE_DIGEST" =~ ^sha256: ]]; then + echo "Registry publication did not return an immutable sha256 image digest; refusing to create a release manifest." >&2 + exit 1 + fi + jq -n \ + --arg tag "$GITHUB_REF_NAME" \ + --arg release_kind "${{ steps.release_kind.outputs.kind }}" \ + --arg commit "$GITHUB_SHA" \ + --arg image "{{OCI_REGISTRY}}/{{IMAGE_NAME}}@$IMAGE_DIGEST" \ + --arg image_tag "{{OCI_REGISTRY}}/{{IMAGE_NAME}}:$GITHUB_REF_NAME" \ + --arg source "https://github.com/$GITHUB_REPOSITORY" \ + --arg build_run "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + --arg healthcheck "{{DOCUMENTED_HEALTHCHECK_NAME}}" \ + --arg platforms "{{OCI_PLATFORMS}}" \ + --arg provenance "BuildKit provenance: mode=max" \ + --arg sbom "BuildKit SBOM: enabled" \ + '{schemaVersion: 1, releaseTag: $tag, releaseKind: $release_kind, commit: $commit, image: $image, imageTag: $image_tag, platforms: ($platforms | split(",")), sourceRepository: $source, buildRun: $build_run, provenance: $provenance, sbom: $sbom, healthCheck: $healthcheck}' \ + > release-manifest.json + + - name: Mint a short-lived GitHub App token for release publication + id: release_token + uses: actions/create-github-app-token@v3 + with: + client-id: ${{ vars.RELEASE_PUBLISH_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_PUBLISH_APP_PRIVATE_KEY }} + permission-contents: write + + - name: Publish the GitHub Release with the manifest + shell: bash + env: + GH_TOKEN: ${{ steps.release_token.outputs.token }} + RELEASE_KIND: ${{ steps.release_kind.outputs.kind }} + run: | + set -euo pipefail + if [[ -z "$GH_TOKEN" ]]; then + echo "The GitHub App installation token is required to publish a release that triggers the deployment workflow. Configure RELEASE_PUBLISH_APP_CLIENT_ID and RELEASE_PUBLISH_APP_PRIVATE_KEY before tagging." >&2 + exit 1 + fi + args=("$GITHUB_REF_NAME" release-manifest.json --target "$GITHUB_SHA" --title "$GITHUB_REF_NAME" --generate-notes) + if [[ "$RELEASE_KIND" == prerelease ]]; then + args+=(--prerelease) + fi + gh release create "${args[@]}" diff --git a/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl b/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl new file mode 100644 index 00000000..06325b7b --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/assets/release-manifest.json.tmpl @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "releaseTag": "{{RELEASE_TAG}}", + "releaseKind": "{{stable|prerelease}}", + "commit": "{{FULL_GIT_COMMIT_SHA}}", + "image": "{{REGISTRY}}/{{IMAGE_NAME}}@{{IMAGE_DIGEST}}", + "imageTag": "{{REGISTRY}}/{{IMAGE_NAME}}:{{RELEASE_TAG}}", + "platforms": ["{{OCI_PLATFORM}}"], + "sourceRepository": "{{GITHUB_REPOSITORY_URL}}", + "buildRun": "{{GITHUB_ACTIONS_RUN_URL}}", + "provenance": "{{PROVENANCE_REFERENCE_OR_POLICY}}", + "sbom": "{{SBOM_REFERENCE_OR_POLICY}}", + "healthCheck": "{{DOCUMENTED_HEALTHCHECK_NAME}}" +} diff --git a/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl b/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl new file mode 100644 index 00000000..c77e171a --- /dev/null +++ b/skills/dockerized-service-release-deployment-workflow/assets/verify-production-health.sh.tmpl @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +deploy_environment=${1:?"Expected the deployment environment, such as test or production."} +image_ref=${2:?"Expected the exact OCI image digest deployed by the release workflow."} + +if [[ ! "$image_ref" =~ ^([^/@[:space:]]+\.[^/@[:space:]]+|localhost|[^/@[:space:]]+:[0-9]+)/[^@[:space:]]+@sha256:[0-9a-f]{64}$ ]]; then + echo "Refusing health verification because '$image_ref' does not include a registry, repository, and immutable OCI sha256 digest." >&2 + exit 1 +fi + +echo "The $deploy_environment health-check template has not been configured. Replace it with a bounded check of the documented readiness endpoint, expected response, timeout, and diagnostic-log location." >&2 +exit 1 diff --git a/uv.lock b/uv.lock index 1c8be527..d98443e1 100644 --- a/uv.lock +++ b/uv.lock @@ -286,7 +286,7 @@ wheels = [ [[package]] name = "socket-maintenance" -version = "9.26.1" +version = "9.27.0" source = { virtual = "." } [package.dev-dependencies]