docs(product): refresh product and release contracts

This commit is contained in:
Tam Nhu Tran committed 2026-07-26 09:34:48 -04:00
1 parent fd45c51ed0
commit b918783293
2 files changed
+284 -393

No files matched your search

+194 -314
View File
@@ -1,374 +1,254 @@
# CCS Product Development Requirements (PDR) # CCS Product Development Requirements
Last Updated: 2026-05-07
## Product Overview ## Product Overview
**Product Name**: CCS (Claude Codex Switch) **Product name:** CCS (Claude Codex Switch)
**Tagline**: The multi-provider profile and runtime manager for Claude Code and compatible CLIs **Purpose:** Provide one profile and runtime-management surface for Claude Code,
Codex CLI, Factory Droid, CLIProxy-backed OAuth providers, and compatible API
profiles.
**Description**: Multi-provider CLI/runtime manager enabling seamless switching between multiple Claude accounts, OAuth/API providers, and alternate targets such as Claude Code, Factory Droid, and Codex CLI. Includes a React-based dashboard for configuration management, plus support for local and remote CLIProxyAPI instances, hybrid quota management, and official Claude channel runtime setup for Telegram, Discord, and iMessage. CCS includes:
**Current Version**: v7.34.x+ (First-class ImageAnalysis MCP tooling, WebSearch MCP, performance improvements) - a TypeScript CLI and local server;
- a React dashboard for configuration, account, health, and usage workflows;
- isolated account contexts and per-profile settings;
- local or remote CLIProxy routing;
- optional managed tools such as WebSearch and image analysis; and
- an integrated Docker image containing CCS, CLIProxy, and the dashboard.
--- Release versions and completed-release inventories belong in
[`CHANGELOG.md`](../CHANGELOG.md), not this evergreen requirements document.
## Problem Statement ## Problem
Developers using Claude Code face these challenges: Developers need to switch between accounts, providers, and compatible CLIs
without repeatedly editing credentials or allowing one session's configuration
to leak into another. They also need a visible, reversible way to manage local
proxy state and diagnose provider readiness.
1. **Single Account Limitation**: Cannot run multiple Claude subscriptions simultaneously ## Product Principles
2. **Provider Lock-in**: Stuck with Anthropic's API, cannot use alternatives
3. **No Concurrent Sessions**: Cannot work on different projects with different accounts
4. **Complex Configuration**: Manual env var and config file management
5. **No Usage Analytics**: Lack visibility into token usage and costs across providers
--- 1. **CLI first:** Core configuration and launch behavior remains scriptable.
2. **Explicit state:** Users can inspect which profile, provider, target, and
proxy mode CCS selected.
3. **Isolation:** Account sessions use separate configuration roots where the
target supports them.
4. **Compatibility:** CCS adapts provider credentials to a target without
redefining the provider or target protocol.
5. **Reversibility:** Persistent writes are explicit and recoverable.
6. **Local ownership:** Credentials and profile state remain on infrastructure
selected by the user.
## Solution ## Users
CCS provides: | User | Primary need |
| --- | --- |
1. **Multi-Account Claude**: Isolated instances via `CLAUDE_CONFIG_DIR` | Individual developer | Separate accounts, projects, and provider profiles |
2. **OAuth Providers**: Zero-config Gemini, Codex, xAI/Grok, Antigravity, Kiro, and other active OAuth integrations, with deprecated Copilot compatibility for existing setups | Consultant or agency | Isolate client contexts |
3. **AI Providers**: Dedicated CLIProxy dashboard for Gemini, Codex, Claude, Vertex, and OpenAI-compatible API-key families | API consumer | Reuse Anthropic-compatible and OpenAI-compatible providers |
4. **API Profiles**: GLM, Kimi, OpenRouter, any Anthropic-compatible API | Power user | Manage OAuth accounts, routing, health, and quota state |
5. **Visual Dashboard**: React SPA for configuration management | Team operator | Run a shared remote CLIProxy or integrated Docker service |
6. **Automatic WebSearch**: First-class local WebSearch tool with deterministic provider chain for third-party providers
7. **Automatic Image Analysis**: First-class local ImageAnalysis tool with direct provider routing for third-party profiles
8. **Usage Analytics**: Token tracking, cost analysis, model breakdown
9. **Official Claude Channels**: Runtime auto-enable plus dashboard token/config flow for Telegram, Discord, and macOS-only iMessage
10. **Routing Strategy Guidance**: First-class `round-robin` vs `fill-first` controls in CLI and dashboard, with explicit opt-in changes and no account-based guessing
---
## Target Users
| User Type | Use Case | Primary Features |
|-----------|----------|------------------|
| Individual Developer | Work/personal separation | Multi-account Claude |
| Agency/Contractor | Client account isolation | Profile switching |
| Cost-conscious Dev | GLM for bulk operations | API profiles, analytics |
| Enterprise | Custom LLM integration | OpenAI-compatible endpoints |
| Power User | Multiple providers | OpenRouter 300+ models |
---
## Functional Requirements ## Functional Requirements
### FR-001: Profile Switching ### FR-001: Profile resolution and launch
- Switch between profiles with `ccs <profile>` command
- Support default profile when no argument provided
- Pass through all Claude CLI arguments
### FR-002: Multi-Account Claude - Launch the default profile with `ccs`.
- Create isolated Claude instances - Launch named settings, account, and CLIProxy profiles.
- Maintain separate sessions, todolists, logs per account - Pass target-specific arguments without losing CCS-owned routing constraints.
- Share commands, skills, agents across accounts - Resolve provider and target aliases through canonical registries.
### FR-003: OAuth Provider Integration ### FR-002: Account isolation
- Support Gemini, Codex, xAI/Grok, Antigravity, Kiro, and deprecated Copilot compatibility OAuth flows
- Browser-based authentication with provider-specific Authorization Code, Device Code, or polling flows
- Token caching and refresh
### FR-004: API Profile Management - Maintain an account registry.
- Configure custom API endpoints - Use isolated `CLAUDE_CONFIG_DIR` roots for Claude account profiles.
- Support Anthropic-compatible APIs - Keep shared resources and instance-owned state distinguishable.
- Model mapping and configuration - Prevent one account's provider overrides from leaking into another launch.
- OpenRouter integration with 300+ models
### FR-004A: CLIProxy AI Provider Management ### FR-003: Provider integration
- Configure CLIProxy-managed Gemini, Codex, Claude, Vertex, and OpenAI-compatible API-key entries
- Keep provider authoring separate from CCS API Profile creation
- Support local config editing and remote CLIProxy management parity where available
### FR-005: Dashboard UI - Support API-key profiles and OAuth-backed CLIProxy providers.
- Visual profile management - Support local and remote CLIProxy operation.
- Real-time health monitoring - Keep original and Plus CLIProxy backends explicit; do not silently substitute
- Usage analytics with cost tracking one when a requested provider requires the other.
- Modular page architecture (settings, analytics, auth-monitor) - Treat provider capability metadata as code-owned, not prose-owned. See
[`src/cliproxy/provider-capabilities.ts`](../src/cliproxy/provider-capabilities.ts)
and
[`src/cliproxy/types/provider-types.ts`](../src/cliproxy/types/provider-types.ts).
### FR-006: Health Diagnostics ### FR-004: Target adapters
- Verify Claude CLI installation
- Check config file integrity
- Validate symlinks and permissions
### FR-007: WebSearch Fallback - Support Claude Code, Factory Droid, and Codex CLI through target adapters.
- Expose a CCS-managed local WebSearch tool for third-party profiles that cannot reach Anthropic's native tool - Deliver credentials in the form owned by each target:
- Suppress native `WebSearch` on third-party launches and steer Claude toward the CCS-owned path when it is available environment variables for Claude launches, managed custom-model state for
- Support Exa, Tavily, Brave, and DuckDuckGo real search backends Droid, and transient configuration overrides for CCS-routed Codex launches.
- Keep Gemini CLI, OpenCode, and Grok as optional legacy fallback - Preserve user-owned target configuration outside the explicitly managed
- Graceful fallback chain fields.
### FR-007A: First-Class Image Analysis ### FR-005: Configuration management
- Expose a CCS-managed local `ImageAnalysis` MCP tool for third-party profiles that need provider-backed vision
- Resolve the provider route before launch and send requests directly to `/api/provider/<backend>/v1/messages`
- Use editable prompt templates for `default`, `screenshot`, and `document` analysis modes
- Suppress the old CCS-managed `Read` hook during healthy MCP launches so it cannot compete with the primary path
- Keep the old `Read` hook as compatibility fallback only when MCP provisioning fails but provider-backed analysis is still viable
- Auto-heal stale CCS-managed image hooks and missing isolated MCP sync through launch-time cleanup, dashboard provisioning, and `ccs doctor --fix`
- Fall back to native `Read` without failing the whole launch when managed runtime, auth, or proxy readiness is unavailable
### FR-008: Remote CLIProxy Support - Store CCS configuration under the directory resolved by
- Connect to remote CLIProxyAPI instances [`getCcsDir()`](../src/utils/config-manager.ts).
- CLI flags for proxy configuration (--proxy-host, --proxy-port, etc.) - Store API profile launch settings in `<profile>.settings.json` files under
- Environment variable configuration (CCS_PROXY_HOST, etc.) that directory.
- Fallback to local proxy when remote unreachable - Require all environment values written to settings files to be strings.
- Protocol-based default ports (443 for HTTPS, 8317 for HTTP) - Keep shared Claude settings unchanged during normal profile launches.
- Dashboard UI for remote server configuration and testing - Allow the explicit `ccs persist` workflow to merge a profile into
`~/.claude/settings.json`; back up the existing file and write atomically.
- Reject unsafe settings-file targets such as symlinks.
### FR-009: Quota Management (v7.14) ### FR-006: Dashboard and diagnostics
- Pause/resume individual accounts via `ccs cliproxy pause/resume <account>`
- Check quota status via `ccs cliproxy status [account]`
- Inspect the current proxy-wide routing strategy via `ccs cliproxy routing`
- Explicitly switch `round-robin` vs `fill-first` from CLI or dashboard
- Keep `round-robin` as the default until the user explicitly changes it
- Never infer routing strategy from account count, tier mix, or paused/default account state
- Auto-failover when account exhausted
- Tier detection: free/pro/ultra/unknown
- Distinguish entitlement failures from temporary capacity exhaustion
- Pre-flight quota checks before session start
- Dashboard UI with pause/resume toggles, tier badges, and quota-detail guidance
### FR-010: Docker Deployment - Provide local APIs and a React UI for supported configuration workflows.
- Multi-stage Dockerfile with bun 1.2.21 and node:20-bookworm-slim - Surface health, authentication, provider, routing, and usage state without
- Docker Compose setup with resource limits and healthcheck exposing credentials.
- Persistent volumes for config, credentials, and CLI tools - Keep dashboard changes aligned with the same configuration contracts used by
- Pre-installed CLIs: claude, gemini, grok, opencode, ccs the CLI.
- Ports: 3000 (Dashboard), 8317 (CLIProxy)
- Entrypoint with privilege dropping and usage help
- Environment variable configuration support
### FR-011: Third-Party Tool Integration ### FR-007: Managed tools
- Export shell-evaluable env vars via `ccs env` command
- Support OpenAI, Anthropic, raw output formats
- Auto-detect shell (bash/zsh, fish, PowerShell) from $SHELL
- Security: single-quoted output, key sanitization, shell-specific escaping
- Cross-platform compatibility (macOS, Linux, Windows)
### FR-012: Official Claude Channels - Provide WebSearch and image-analysis integration for profiles that need
- Support Telegram, Discord, and iMessage selection via `ccs config channels` and the dashboard CCS-managed alternatives.
- Auto-inject `--channels` only for native Claude `default` and `account` sessions - Prefer explicit provider routes.
- Store Telegram/Discord bot tokens in Claude's own `~/.claude/channels/<channel>/.env` state or the official `*_STATE_DIR` override path when one is configured - Fail closed when enabled third-party WebSearch cannot prepare its constrained
- Treat iMessage as macOS-only, tokenless, and dependent on Claude-side install plus OS permissions MCP replacement.
- Require Bun, Claude Code v2.1.80+, and verified `claude.ai` auth before runtime auto-enable - Allow image analysis to use compatible native behavior when its managed route
- Keep `--dangerously-skip-permissions` optional and never add it when the user already made an explicit permission choice is unavailable.
- Surface platform/auth/version/setup blockers clearly in both CLI and dashboard flows
- Preserve dashboard token drafts when save/refresh fails, and let already-selected unsupported iMessage entries be turned off without allowing re-enable on unsupported platforms
--- ### FR-008: Remote proxy
- Resolve remote proxy settings from supported CLI flags, environment
variables, and CCS configuration.
- Verify reachability before use.
- Fall back to a local proxy only when fallback is enabled.
- Fail instead of falling back when remote-only mode is selected.
### FR-009: Quota and account-pool management
- Display supported quota and account health data.
- Let users pause and resume accounts.
- Keep routing-strategy changes explicit.
- Temporarily remove exhausted accounts from rotation only under the
CCS-managed cooldown contract and restore only CCS-created pauses.
### FR-010: Docker deployment
- Publish an integrated multi-architecture image containing CCS, CLIProxy, and
the dashboard.
- Persist CCS state and service logs in declared volumes.
- Expose dashboard and CLIProxy service ports.
- Health-check both services.
- Do not bundle target AI CLIs into the integrated image; consumers that need
them run sibling containers or install them separately.
### FR-011: Shell and editor integration
- Export shell-safe environment values through `ccs env`.
- Support the documented shell output formats.
- Keep persistent shared settings behind `ccs persist`.
- Keep editor-specific writes scoped to the selected editor and user layer.
## Non-Functional Requirements ## Non-Functional Requirements
### NFR-001: Performance ### NFR-001: Security
- CLI startup < 100ms
- Dashboard load < 2s - Do not print or log credentials.
- Minimal memory footprint - Bind local proxy services to loopback unless the user explicitly selects a
deployment that exposes them.
- Validate file types and use safe replacement semantics for managed sensitive
configuration.
- Keep remote transport and authentication choices explicit.
### NFR-002: Reliability ### NFR-002: Reliability
- Idempotent operations
- Graceful error handling
- Automatic recovery where possible
### NFR-003: Security - Make setup and repair operations idempotent where practical.
- Local-only proxy binding (127.0.0.1) - Preserve existing user state when merging managed configuration.
- No credential exposure in logs - Report recovery actions in actionable error messages.
- Secure token storage - Clean up child processes and temporary runtime state on exit.
### NFR-004: Cross-Platform ### NFR-003: Portability
- Support Linux, macOS, Windows
- Bash 3.2+, PowerShell 5.1+, Node.js 14+
- Identical behavior across platforms
### NFR-005: Maintainability - Support macOS, Linux, and Windows for the host CLI where target dependencies
- Files < 200 lines (with documented exceptions) allow.
- Domain-based organization - Support Node.js 18 or newer, as declared by
- Barrel exports for clean imports [`package.json`](../package.json).
- 90%+ test coverage - Support Bun 1.0 or newer for development and supported runtime workflows.
- Keep terminal output ASCII-only and respect `NO_COLOR` and TTY detection.
--- ### NFR-004: Maintainability
## Technical Requirements - Keep provider metadata centralized.
- Use target adapters instead of target checks scattered through dispatch code.
- Validate CLI, server, and dashboard contracts with focused tests.
- Keep generated or volatile inventories out of evergreen architecture prose.
### TR-001: Runtime Dependencies ## Runtime and Deployment Requirements
- Node.js 14+ or Bun 1.0+
- Claude Code CLI installed
- Internet access for OAuth/API calls
### TR-002: Optional Dependencies | Surface | Requirement |
- CLIProxyAPI binary (auto-managed) | --- | --- |
- Exa/Tavily/Brave API keys for higher-quality WebSearch | Host npm install | Node.js 18+ |
- Gemini CLI for legacy WebSearch fallback | Development and repository gates | Bun 1.0+ and Node.js 18+ |
- Bun plus Claude Code v2.1.80+ with `claude.ai` auth for Official Channels auto-enable | Claude launches | Claude Code installed and authenticated as required by the selected profile |
| Droid launches | Factory Droid installed |
### TR-003: Configuration | Codex launches | Codex CLI installed |
- YAML-based config (`~/.ccs/config.yaml`) | Local OAuth proxy | CCS-managed CLIProxy binary |
- JSON settings per profile | Integrated Docker | Docker or compatible container runtime; target CLIs are not bundled |
- Environment variable overrides
- Official channel bot tokens stored in Claude-managed `~/.claude/channels/<channel>/.env`
---
## Architecture Constraints ## Architecture Constraints
### AC-001: CLI-First Design ### AC-001: Profile state and shared settings are separate
- All features accessible via CLI
- Dashboard is convenience layer, not required
- Scriptable and automatable
### AC-002: Non-Invasive Normal launches consume CCS-owned profile files and environment state.
- Never modify `~/.claude/settings.json` `~/.claude/settings.json` is written only through an explicit persistence or
- Use environment variables for configuration approved settings-management workflow.
- Reversible changes only
### AC-003: Proxy Pattern ### AC-002: Provider and target are independent axes
- Use local proxy for provider routing
- Claude CLI communicates with localhost
- Proxy handles upstream API calls
--- A provider supplies credentials and routing. A target adapter determines how a
compatible CLI receives them. Unsupported combinations must fail clearly.
## Success Metrics ### AC-003: Proxy trust boundary is visible
| Metric | Target | Current | Local CLIProxy, remote CLIProxy, and direct API profiles have different
|--------|--------|---------| transport and credential boundaries. CCS must not present them as equivalent or
| Startup time | < 100ms | Achieved | silently cross those boundaries.
| Dashboard load | < 2s | Achieved |
| Error rate | < 1% | Achieved |
| Test coverage | > 90% | 90% (1440 tests, 6 skipped) |
| File size compliance | 100% < 200 lines | 95% |
--- ### AC-004: Source owns volatile capability data
## Release Criteria Provider IDs, aliases, OAuth flow types, callback ports, refresh ownership,
backend restrictions, and quota support must be read from the provider
registries and tests. Documentation describes how to find them rather than
copying a second mutable table.
### v1.0 Release (Complete) ## Acceptance Criteria
- [x] Multi-account Claude support
- [x] OAuth provider integration (Gemini, Codex, AGY)
- [x] API profile management
- [x] Dashboard UI
- [x] Health diagnostics
- [x] WebSearch fallback
- [x] Cross-platform support
### v7.0 Release (Complete) - A user can create or select a supported profile and launch it on a compatible
- [x] OpenRouter integration with 300+ models target without manual credential-file editing.
- [x] Interactive model picker - Concurrent account profiles do not share target session state accidentally.
- [x] Dynamic model discovery - Local and remote proxy failures follow the configured fallback policy.
- [x] Tier mapping (opus/sonnet/haiku) - Persistent settings writes preserve unrelated keys and create a recovery
- [x] Settings page modularization (20 files) path.
- [x] Analytics page modularization (8 files) - Dashboard operations produce configuration compatible with CLI operations.
- [x] Auth monitor modularization (8 files) - Release automation publishes only from the documented branches and lanes.
- [x] Comprehensive test infrastructure (539 CLI + 99 UI tests) - Documentation links resolve and architecture claims are traceable to source.
### v7.1 Release (Complete)
- [x] Remote CLIProxy routing support
- [x] CLI flags for remote proxy (--proxy-host, --proxy-port, etc.)
- [x] Environment variables for proxy config (CCS_PROXY_*)
- [x] Dashboard remote proxy configuration UI
- [x] Connection testing with latency display
- [x] Fallback to local when remote unreachable
- [x] Protocol-based default ports (HTTPS:443, HTTP:8317)
### v7.2 Release (Complete)
- [x] Kiro (AWS) OAuth provider support via CLIProxyAPIPlus
- [x] GitHub Copilot (ghcp) OAuth provider via Device Code flow (deprecated compatibility)
- [x] Authorization Code flow for Kiro (port 9876)
- [x] Device Code flow for ghcp (no local port needed)
### v7.14 Release (Complete)
- [x] Hybrid quota management with auto-failover
- [x] `ccs cliproxy pause/resume/status` commands
- [x] API tier detection (free/pro/ultra/unknown)
- [x] Dashboard pause/resume toggles and tier badges
- [x] Pre-flight quota checks before session start
### v7.23 Release (Complete)
- [x] Docker deployment support (PR #345)
- [x] Multi-stage Dockerfile with bun 1.2.21
- [x] Docker Compose with resource limits and healthcheck
- [x] Persistent volumes for config and credentials
- [x] Pre-installed AI CLI tools (claude, gemini, grok, opencode)
- [x] Entrypoint with privilege dropping
### v7.34 Release (Complete)
- [x] First-class `ImageAnalysis` MCP tool for third-party launches
- [x] Direct provider-scoped routing for image analysis requests
- [x] Prompt template selection for default / screenshot / document flows
- [x] Hook fallback retained only for compatibility
- [x] Non-fatal native `Read` fallback when managed runtime is unavailable
- [x] `ccs config image-analysis` CLI command
- [x] Doctor integration for hook validation
- [x] 791-line E2E test suite for image analysis
- [x] Performance: Replace busy-wait with Atomics.wait in config lock
- [x] Network error handling with noRetryPatterns
- [x] Quota 429 rate limit handling improvements
- [x] WebSocket maxPayload limit (DoS prevention)
### v7.39 Release (Complete)
- [x] `ccs env` command for third-party tool integration (OpenCode, Cursor, Continue)
- [x] Multi-format output: openai, anthropic, raw
- [x] Multi-shell support: bash/zsh, fish, PowerShell (auto-detected)
- [x] CLIProxy profile support (gemini, codex, agy, qwen)
- [x] Settings profile support (glm, kimi, custom API)
- [x] Security: single-quoted output, key sanitization, shell-specific escaping
- [x] Shell completion updated (bash, zsh, fish, PowerShell)
- [x] 34 unit tests for env command
### v8.0 Release (Planned - Q1 2026)
- [ ] Multiple CLIProxyAPI instances (load balancing, failover)
- [ ] Native git worktree support
- [ ] Critical bug fixes (#158, #155, #124)
### v9.0 Release (Future - Q2 2026)
- [ ] Team collaboration features
- [ ] Cloud sync for profiles
- [ ] Plugin system
- [ ] CLI extension framework
---
## Dependencies
### External Services
- Anthropic Claude API
- Google Gemini API
- GitHub Codex API
- GitHub Copilot (ghcp - deprecated Device Code OAuth compatibility)
- AWS Kiro (Authorization Code OAuth)
- Z.AI GLM API
- OpenRouter API
- Moonshot Kimi API
- DeepSeek API
- Alibaba Qwen API
- Minimax API
- Azure Foundry API
### Third-Party Libraries
- Express.js (web server)
- React (dashboard)
- Vite (build tool)
- shadcn/ui (UI components)
- CLIProxyAPI (proxy binary)
- Vitest (testing)
---
## Risks and Mitigations ## Risks and Mitigations
| Risk | Probability | Impact | Mitigation | | Risk | Mitigation |
|------|-------------|--------|------------| | --- | --- |
| Claude CLI API changes | Medium | High | Version pinning, compatibility layer | | Provider auth contracts change | Central capability registry plus provider-specific tests |
| Provider API deprecation | Low | High | Fallback chain, multiple providers | | Target CLI configuration changes | Adapter boundary and compatibility checks |
| OAuth token expiry | Medium | Medium | Auto-refresh, clear error messages | | Credential leakage | Local storage, redaction, safe file handling, no secret logging |
| Binary compatibility | Low | Medium | Multi-platform builds, fallback | | Remote proxy outage | Reachability checks and explicit fallback policy |
| Configuration corruption | Validation, backup, locking, and atomic replacement where supported |
--- | Documentation drift | Link volatile details to code; keep this document version-neutral |
## Related Documentation ## Related Documentation
- [Codebase Summary](./codebase-summary.md) - Technical structure - [Codebase Summary](./codebase-summary.md)
- [Code Standards](./code-standards.md) - Development conventions - [Code Standards](./code-standards.md)
- [System Architecture](./system-architecture/index.md) - Architecture diagrams - [System Architecture](./system-architecture/index.md)
- [Project Roadmap](./project-roadmap.md) - Development phases and GitHub issues - [Provider Flows](./system-architecture/provider-flows.md)
- [Release Process](./release-process.md)
- [Project Roadmap](./project-roadmap.md)
+90 -79
View File
@@ -1,106 +1,117 @@
# CCS Release Process # CCS Release Process
CCS uses a decoupled release model: every merge to `main` immediately publishes CCS has separate development, stable npm, and Docker promotion lanes. A branch
a stable npm `@latest` release and an immutable Docker `:<ver>` tag. Docker push starts the relevant workflow. Eligible `dev` pushes publish the next custom
mutable tags (`:latest`, `:<MAJOR>`, `:<MINOR>`) require a separate manual development prerelease after their gates pass; `main` publishes only when
promote step after an operator-verified soak window. This decouples the npm semantic-release finds release-worthy commits.
ecosystem from the Docker stability gate.
## Phase 1 — Automatic stable release (on every merge to `main`) ## Release lanes
1. A PR is merged into `main` with a conventional commit (`feat:`, `fix:`, etc.). | Source | Workflow | Result |
2. `release.yml` triggers semantic-release, which reads `.releaserc.cjs`. | --- | --- | --- |
3. Because `main` is a stable channel, semantic-release cuts a GitHub release | Push to `dev` | [`dev-release.yml`](../.github/workflows/dev-release.yml) | Custom development prerelease and npm `@dev` publication |
tagged `vX.Y.Z` and publishes the npm package to the `@latest` dist-tag | Push to `main` | [`release.yml`](../.github/workflows/release.yml) | Semantic-release stable version, npm `@latest`, tag, and GitHub release when commits require a release |
immediately. No rc channel, no soak delay on npm. | Published stable or `rc` GitHub release | [`docker-release.yml`](../.github/workflows/docker-release.yml) | Immutable integrated Docker version tag, signature, and smoke test |
4. `docker-release.yml` triggers on the `release: published` event and: | Manual stable promotion | [`promote-release.yml`](../.github/workflows/promote-release.yml) | Docker `:latest`, major, and minor aliases |
- Validates the tag as stable semver (`vX.Y.Z`). | Stable GitHub release | [`sync-dev-after-release.yml`](../.github/workflows/sync-dev-after-release.yml) | Merge released `main` state back into `dev` |
- Builds the integrated image for `linux/amd64` and `linux/arm64`.
- Pushes **only the immutable** `ghcr.io/kaitranntt/ccs:X.Y.Z` tag.
- Signs the image with cosign (keyless OIDC).
- Runs smoke tests (`smoke-test` job).
- Mutable tags (`:latest`, `:<MAJOR>`, `:<MINOR>`) are **not** added at
this stage — `promote-mutable-tags` only runs on explicit
`workflow_dispatch` with `promote_to_latest=true`.
## Phase 2 — Manual promotion to Docker mutable tags (rc.1 soak window) ## Development prereleases
After the immutable `:<ver>` Docker image has soaked (typically 24 h with no `Dev Release` runs on pushes to `dev` and can also be dispatched manually.
reported issues), the operator promotes mutable tags: After build and validation gates, it calls
[`scripts/dev-release.sh`](../scripts/dev-release.sh). That script owns the
`<stable>-dev.<n>` version sequence and npm `@dev` publication. It is
intentionally separate from the production semantic-release configuration.
1. Verify the immutable image is healthy: Generated `chore(release): ...` pushes are skipped by the workflow guard to
prevent release recursion.
```bash ## Stable npm and GitHub releases
docker pull ghcr.io/kaitranntt/ccs:X.Y.Z
docker run --rm -p 3000:3000 -p 8317:8317 ghcr.io/kaitranntt/ccs:X.Y.Z
# check http://localhost:3000 and http://localhost:8317
```
2. Optionally verify the cosign signature: `Release` runs on `main`. It builds the CLI and dashboard, runs the fast, slow,
and end-to-end gates, then invokes semantic-release with
[`.releaserc.cjs`](../.releaserc.cjs).
```bash Semantic-release analyzes commits since the previous stable release:
cosign verify \
--certificate-identity-regexp "https://github.com/kaitranntt/ccs/.github/workflows/docker-release.yml" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/kaitranntt/ccs:X.Y.Z
```
3. Run the `promote-release` workflow via GitHub Actions UI or CLI: - `feat` produces at least a minor release;
- `fix`, `hotfix`, `refactor`, and `style` produce patch releases under the
repository rules;
- breaking-change notation produces the appropriate major release; and
- commits without a matching release rule may produce no release.
```bash When a release is required, the lane updates `CHANGELOG.md` and `package.json`,
gh workflow run promote-release.yml \ publishes npm `@latest`, creates the stable Git tag and GitHub release, and
--field tag=vX.Y.Z pushes the generated release commit to `main`. Do not bump versions or create
``` release tags manually.
This dispatches `docker-release.yml` with `promote_to_latest=true`, which ## Docker publication and promotion
triggers the `promote-mutable-tags` job to add `:latest`, `:<MAJOR>`, and
`:<MINOR>` via `docker buildx imagetools create`.
Alternatively, dispatch `docker-release.yml` directly: The supported integrated image is `ghcr.io/kaitranntt/ccs`.
```bash On a published stable `vX.Y.Z` or release-candidate `vX.Y.Z-rc.N` GitHub
gh workflow run "Publish Docker Image" \ release, `Publish Docker Image`:
--field tag=vX.Y.Z \
--field promote_to_latest=true
```
## Why npm and Docker have different soak windows 1. validates the release tag;
2. checks out that tag;
3. builds the integrated image for `linux/amd64` and `linux/arm64`;
4. publishes only the matching immutable version tag;
5. signs the image digest with keyless cosign; and
6. smoke-tests the published image.
- **npm `@latest`**: Published immediately on every `main` merge. npm users who Mutable aliases are a separate operator decision. After verifying the immutable
pin a version are unaffected; users who run `npm install -g @kaitranntt/ccs` image and allowing the desired soak period, dispatch `promote-release.yml`:
get the latest immediately. Rollback is `npm install -g @kaitranntt/ccs@X.Y.Z`.
- **Docker `:latest`**: Promoted only after operator confirmation. Users who
pull `:latest` or run `docker pull` without a pinned tag are shielded from
a bad image. The immutable `:<ver>` tag is always available for pinned usage
from the moment of release.
## Verifying the promotion
```bash ```bash
# Confirm :latest points to the promoted digest gh workflow run promote-release.yml --field tag=vX.Y.Z
docker buildx imagetools inspect ghcr.io/kaitranntt/ccs:latest ```
# Confirm npm @latest updated (happens automatically at Phase 1) The promotion workflow verifies that the stable GitHub release and immutable
image exist, then dispatches `docker-release.yml` with
`promote_to_latest=true`. The promotion job creates `:latest`, `:X`, and
`:X.Y` aliases from the immutable image digest.
The deprecated `ccs-dashboard` image has its own sunset compatibility job.
Do not use its tag behavior as the contract for the supported integrated image.
## Post-release development sync
A published, non-prerelease `vX.Y.Z` release targeting `main` triggers
`Sync Dev After Main Release`. The workflow merges `main` into `dev`, resolves
known generated version-file conflicts in favor of the released `main` state,
and pushes `dev`. That push intentionally triggers the normal `Push CI` and
development-release lanes.
## Verification
```bash
# npm channels
npm view @kaitranntt/ccs dist-tags npm view @kaitranntt/ccs dist-tags
# immutable integrated image
docker buildx imagetools inspect ghcr.io/kaitranntt/ccs:X.Y.Z
# mutable alias after promotion
docker buildx imagetools inspect ghcr.io/kaitranntt/ccs:latest
``` ```
## Rollback Verify the GitHub Actions run and tag point to the expected commit before
announcing a release.
If a promoted release is found to be bad: ## Recovery
```bash - **Bad npm release:** publish a corrected patch. Do not unpublish a version
# Repoint :latest to the previous known-good immutable tag used by downstream consumers.
docker buildx imagetools create \ - **Bad immutable Docker image:** leave the immutable tag unchanged and publish
--tag ghcr.io/kaitranntt/ccs:latest \ a corrected version.
ghcr.io/kaitranntt/ccs:PREVIOUS.VERSION - **Bad mutable Docker promotion:** promote a known-good immutable digest back
to the mutable aliases through the controlled workflow.
- **Failed `dev` sync:** repair the merge against current `main` and `dev`;
never overwrite branch history.
# For npm, publish a fix as a new patch release (do not unpublish) ## Branch and tag summary
# Unpublishing npm packages causes downstream breakage for pinned consumers.
```
## Branch / tag taxonomy | Branch | Package channel | npm dist-tag | Integrated Docker |
| --- | --- | --- | --- |
| Branch | Semantic-release channel | npm dist-tag | Docker tag (on release event) | Docker mutable (on promote) | | `dev` | Development prerelease | `@dev` | None |
|--------|--------------------------|--------------|-------------------------------|------------------------------| | `main` | Stable semantic release | `@latest` | Immutable tag on GitHub release; mutable aliases after manual promotion |
| `main` | stable | `@latest` | `:<ver>` (immutable, immediate) | `:latest`, `:<MAJOR>`, `:<MINOR>` (after soak) |
| `dev` | `dev` prerelease | `@dev` | not published | not published |