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)
Last Updated: 2026-05-07
# CCS Product Development Requirements
## 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
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
## Product Principles
---
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:
1. **Multi-Account Claude**: Isolated instances via `CLAUDE_CONFIG_DIR`
2. **OAuth Providers**: Zero-config Gemini, Codex, xAI/Grok, Antigravity, Kiro, and other active OAuth integrations, with deprecated Copilot compatibility for existing setups
3. **AI Providers**: Dedicated CLIProxy dashboard for Gemini, Codex, Claude, Vertex, and OpenAI-compatible API-key families
4. **API Profiles**: GLM, Kimi, OpenRouter, any Anthropic-compatible API
5. **Visual Dashboard**: React SPA for configuration management
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 |
---
| User | Primary need |
| --- | --- |
| Individual developer | Separate accounts, projects, and provider profiles |
| Consultant or agency | Isolate client contexts |
| API consumer | Reuse Anthropic-compatible and OpenAI-compatible providers |
| Power user | Manage OAuth accounts, routing, health, and quota state |
| Team operator | Run a shared remote CLIProxy or integrated Docker service |
## Functional Requirements
### FR-001: Profile Switching
- Switch between profiles with `ccs <profile>` command
- Support default profile when no argument provided
- Pass through all Claude CLI arguments
### FR-001: Profile resolution and launch
### FR-002: Multi-Account Claude
- Create isolated Claude instances
- Maintain separate sessions, todolists, logs per account
- Share commands, skills, agents across accounts
- Launch the default profile with `ccs`.
- Launch named settings, account, and CLIProxy profiles.
- Pass target-specific arguments without losing CCS-owned routing constraints.
- Resolve provider and target aliases through canonical registries.
### FR-003: OAuth Provider Integration
- 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-002: Account isolation
### FR-004: API Profile Management
- Configure custom API endpoints
- Support Anthropic-compatible APIs
- Model mapping and configuration
- OpenRouter integration with 300+ models
- Maintain an account registry.
- Use isolated `CLAUDE_CONFIG_DIR` roots for Claude account profiles.
- Keep shared resources and instance-owned state distinguishable.
- Prevent one account's provider overrides from leaking into another launch.
### FR-004A: CLIProxy AI Provider Management
- 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-003: Provider integration
### FR-005: Dashboard UI
- Visual profile management
- Real-time health monitoring
- Usage analytics with cost tracking
- Modular page architecture (settings, analytics, auth-monitor)
- Support API-key profiles and OAuth-backed CLIProxy providers.
- Support local and remote CLIProxy operation.
- Keep original and Plus CLIProxy backends explicit; do not silently substitute
one when a requested provider requires the other.
- 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
- Verify Claude CLI installation
- Check config file integrity
- Validate symlinks and permissions
### FR-004: Target adapters
### FR-007: WebSearch Fallback
- Expose a CCS-managed local WebSearch tool for third-party profiles that cannot reach Anthropic's native tool
- Suppress native `WebSearch` on third-party launches and steer Claude toward the CCS-owned path when it is available
- Support Exa, Tavily, Brave, and DuckDuckGo real search backends
- Keep Gemini CLI, OpenCode, and Grok as optional legacy fallback
- Graceful fallback chain
- Support Claude Code, Factory Droid, and Codex CLI through target adapters.
- Deliver credentials in the form owned by each target:
environment variables for Claude launches, managed custom-model state for
Droid, and transient configuration overrides for CCS-routed Codex launches.
- Preserve user-owned target configuration outside the explicitly managed
fields.
### FR-007A: First-Class Image Analysis
- 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-005: Configuration management
### FR-008: Remote CLIProxy Support
- Connect to remote CLIProxyAPI instances
- CLI flags for proxy configuration (--proxy-host, --proxy-port, etc.)
- Environment variable configuration (CCS_PROXY_HOST, etc.)
- Fallback to local proxy when remote unreachable
- Protocol-based default ports (443 for HTTPS, 8317 for HTTP)
- Dashboard UI for remote server configuration and testing
- Store CCS configuration under the directory resolved by
[`getCcsDir()`](../src/utils/config-manager.ts).
- Store API profile launch settings in `<profile>.settings.json` files under
that directory.
- Require all environment values written to settings files to be strings.
- Keep shared Claude settings unchanged during normal profile launches.
- 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)
- 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-006: Dashboard and diagnostics
### FR-010: Docker Deployment
- Multi-stage Dockerfile with bun 1.2.21 and node:20-bookworm-slim
- Docker Compose setup with resource limits and healthcheck
- Persistent volumes for config, credentials, and CLI tools
- Pre-installed CLIs: claude, gemini, grok, opencode, ccs
- Ports: 3000 (Dashboard), 8317 (CLIProxy)
- Entrypoint with privilege dropping and usage help
- Environment variable configuration support
- Provide local APIs and a React UI for supported configuration workflows.
- Surface health, authentication, provider, routing, and usage state without
exposing credentials.
- Keep dashboard changes aligned with the same configuration contracts used by
the CLI.
### FR-011: Third-Party Tool Integration
- 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-007: Managed tools
### FR-012: Official Claude Channels
- Support Telegram, Discord, and iMessage selection via `ccs config channels` and the dashboard
- Auto-inject `--channels` only for native Claude `default` and `account` sessions
- 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
- Treat iMessage as macOS-only, tokenless, and dependent on Claude-side install plus OS permissions
- Require Bun, Claude Code v2.1.80+, and verified `claude.ai` auth before runtime auto-enable
- Keep `--dangerously-skip-permissions` optional and never add it when the user already made an explicit permission choice
- 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
- Provide WebSearch and image-analysis integration for profiles that need
CCS-managed alternatives.
- Prefer explicit provider routes.
- Fail closed when enabled third-party WebSearch cannot prepare its constrained
MCP replacement.
- Allow image analysis to use compatible native behavior when its managed route
is unavailable.
---
### 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
### NFR-001: Performance
- CLI startup < 100ms
- Dashboard load < 2s
- Minimal memory footprint
### NFR-001: Security
- Do not print or log credentials.
- 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
- Idempotent operations
- Graceful error handling
- Automatic recovery where possible
### NFR-003: Security
- Local-only proxy binding (127.0.0.1)
- No credential exposure in logs
- Secure token storage
- Make setup and repair operations idempotent where practical.
- Preserve existing user state when merging managed configuration.
- Report recovery actions in actionable error messages.
- Clean up child processes and temporary runtime state on exit.
### NFR-004: Cross-Platform
- Support Linux, macOS, Windows
- Bash 3.2+, PowerShell 5.1+, Node.js 14+
- Identical behavior across platforms
### NFR-003: Portability
### NFR-005: Maintainability
- Files < 200 lines (with documented exceptions)
- Domain-based organization
- Barrel exports for clean imports
- 90%+ test coverage
- Support macOS, Linux, and Windows for the host CLI where target dependencies
allow.
- Support Node.js 18 or newer, as declared by
[`package.json`](../package.json).
- 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
- Node.js 14+ or Bun 1.0+
- Claude Code CLI installed
- Internet access for OAuth/API calls
## Runtime and Deployment Requirements
### TR-002: Optional Dependencies
- CLIProxyAPI binary (auto-managed)
- Exa/Tavily/Brave API keys for higher-quality WebSearch
- Gemini CLI for legacy WebSearch fallback
- Bun plus Claude Code v2.1.80+ with `claude.ai` auth for Official Channels auto-enable
### TR-003: Configuration
- YAML-based config (`~/.ccs/config.yaml`)
- JSON settings per profile
- Environment variable overrides
- Official channel bot tokens stored in Claude-managed `~/.claude/channels/<channel>/.env`
---
| Surface | Requirement |
| --- | --- |
| Host npm install | Node.js 18+ |
| Development and repository gates | Bun 1.0+ and Node.js 18+ |
| Claude launches | Claude Code installed and authenticated as required by the selected profile |
| Droid launches | Factory Droid installed |
| Codex launches | Codex CLI installed |
| Local OAuth proxy | CCS-managed CLIProxy binary |
| Integrated Docker | Docker or compatible container runtime; target CLIs are not bundled |
## Architecture Constraints
### AC-001: CLI-First Design
- All features accessible via CLI
- Dashboard is convenience layer, not required
- Scriptable and automatable
### AC-001: Profile state and shared settings are separate
### AC-002: Non-Invasive
- Never modify `~/.claude/settings.json`
- Use environment variables for configuration
- Reversible changes only
Normal launches consume CCS-owned profile files and environment state.
`~/.claude/settings.json` is written only through an explicit persistence or
approved settings-management workflow.
### AC-003: Proxy Pattern
- Use local proxy for provider routing
- Claude CLI communicates with localhost
- Proxy handles upstream API calls
### AC-002: Provider and target are independent axes
---
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 |
|--------|--------|---------|
| Startup time | < 100ms | Achieved |
| Dashboard load | < 2s | Achieved |
| Error rate | < 1% | Achieved |
| Test coverage | > 90% | 90% (1440 tests, 6 skipped) |
| File size compliance | 100% < 200 lines | 95% |
Local CLIProxy, remote CLIProxy, and direct API profiles have different
transport and credential boundaries. CCS must not present them as equivalent or
silently cross those boundaries.
---
### 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)
- [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
## Acceptance Criteria
### v7.0 Release (Complete)
- [x] OpenRouter integration with 300+ models
- [x] Interactive model picker
- [x] Dynamic model discovery
- [x] Tier mapping (opus/sonnet/haiku)
- [x] Settings page modularization (20 files)
- [x] Analytics page modularization (8 files)
- [x] Auth monitor modularization (8 files)
- [x] Comprehensive test infrastructure (539 CLI + 99 UI tests)
### 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)
---
- A user can create or select a supported profile and launch it on a compatible
target without manual credential-file editing.
- Concurrent account profiles do not share target session state accidentally.
- Local and remote proxy failures follow the configured fallback policy.
- Persistent settings writes preserve unrelated keys and create a recovery
path.
- Dashboard operations produce configuration compatible with CLI operations.
- Release automation publishes only from the documented branches and lanes.
- Documentation links resolve and architecture claims are traceable to source.
## Risks and Mitigations
| Risk | Probability | Impact | Mitigation |
|------|-------------|--------|------------|
| Claude CLI API changes | Medium | High | Version pinning, compatibility layer |
| Provider API deprecation | Low | High | Fallback chain, multiple providers |
| OAuth token expiry | Medium | Medium | Auto-refresh, clear error messages |
| Binary compatibility | Low | Medium | Multi-platform builds, fallback |
---
| Risk | Mitigation |
| --- | --- |
| Provider auth contracts change | Central capability registry plus provider-specific tests |
| Target CLI configuration changes | Adapter boundary and compatibility checks |
| Credential leakage | Local storage, redaction, safe file handling, no secret logging |
| 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
- [Codebase Summary](./codebase-summary.md) - Technical structure
- [Code Standards](./code-standards.md) - Development conventions
- [System Architecture](./system-architecture/index.md) - Architecture diagrams
- [Project Roadmap](./project-roadmap.md) - Development phases and GitHub issues
- [Codebase Summary](./codebase-summary.md)
- [Code Standards](./code-standards.md)
- [System Architecture](./system-architecture/index.md)
- [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 uses a decoupled release model: every merge to `main` immediately publishes
a stable npm `@latest` release and an immutable Docker `:<ver>` tag. Docker
mutable tags (`:latest`, `:<MAJOR>`, `:<MINOR>`) require a separate manual
promote step after an operator-verified soak window. This decouples the npm
ecosystem from the Docker stability gate.
CCS has separate development, stable npm, and Docker promotion lanes. A branch
push starts the relevant workflow. Eligible `dev` pushes publish the next custom
development prerelease after their gates pass; `main` publishes only when
semantic-release finds release-worthy commits.
## 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.).
2. `release.yml` triggers semantic-release, which reads `.releaserc.cjs`.
3. Because `main` is a stable channel, semantic-release cuts a GitHub release
tagged `vX.Y.Z` and publishes the npm package to the `@latest` dist-tag
immediately. No rc channel, no soak delay on npm.
4. `docker-release.yml` triggers on the `release: published` event and:
- Validates the tag as stable semver (`vX.Y.Z`).
- 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`.
| Source | Workflow | Result |
| --- | --- | --- |
| Push to `dev` | [`dev-release.yml`](../.github/workflows/dev-release.yml) | Custom development prerelease and npm `@dev` publication |
| Push to `main` | [`release.yml`](../.github/workflows/release.yml) | Semantic-release stable version, npm `@latest`, tag, and GitHub release when commits require a release |
| Published stable or `rc` GitHub release | [`docker-release.yml`](../.github/workflows/docker-release.yml) | Immutable integrated Docker version tag, signature, and smoke test |
| Manual stable promotion | [`promote-release.yml`](../.github/workflows/promote-release.yml) | Docker `:latest`, major, and minor aliases |
| Stable GitHub release | [`sync-dev-after-release.yml`](../.github/workflows/sync-dev-after-release.yml) | Merge released `main` state back into `dev` |
## 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
reported issues), the operator promotes mutable tags:
`Dev Release` runs on pushes to `dev` and can also be dispatched manually.
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
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
```
## Stable npm and GitHub releases
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
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
```
Semantic-release analyzes commits since the previous stable release:
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
gh workflow run promote-release.yml \
--field tag=vX.Y.Z
```
When a release is required, the lane updates `CHANGELOG.md` and `package.json`,
publishes npm `@latest`, creates the stable Git tag and GitHub release, and
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
triggers the `promote-mutable-tags` job to add `:latest`, `:<MAJOR>`, and
`:<MINOR>` via `docker buildx imagetools create`.
## Docker publication and promotion
Alternatively, dispatch `docker-release.yml` directly:
The supported integrated image is `ghcr.io/kaitranntt/ccs`.
```bash
gh workflow run "Publish Docker Image" \
--field tag=vX.Y.Z \
--field promote_to_latest=true
```
On a published stable `vX.Y.Z` or release-candidate `vX.Y.Z-rc.N` GitHub
release, `Publish Docker Image`:
## 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
pin a version are unaffected; users who run `npm install -g @kaitranntt/ccs`
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
Mutable aliases are a separate operator decision. After verifying the immutable
image and allowing the desired soak period, dispatch `promote-release.yml`:
```bash
# Confirm :latest points to the promoted digest
docker buildx imagetools inspect ghcr.io/kaitranntt/ccs:latest
gh workflow run promote-release.yml --field tag=vX.Y.Z
```
# 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
# 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
# Repoint :latest to the previous known-good immutable tag
docker buildx imagetools create \
--tag ghcr.io/kaitranntt/ccs:latest \
ghcr.io/kaitranntt/ccs:PREVIOUS.VERSION
- **Bad npm release:** publish a corrected patch. Do not unpublish a version
used by downstream consumers.
- **Bad immutable Docker image:** leave the immutable tag unchanged and publish
a corrected 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)
# Unpublishing npm packages causes downstream breakage for pinned consumers.
```
## Branch and tag summary
## Branch / tag taxonomy
| Branch | Semantic-release channel | npm dist-tag | Docker tag (on release event) | Docker mutable (on promote) |
|--------|--------------------------|--------------|-------------------------------|------------------------------|
| `main` | stable | `@latest` | `:<ver>` (immutable, immediate) | `:latest`, `:<MAJOR>`, `:<MINOR>` (after soak) |
| `dev` | `dev` prerelease | `@dev` | not published | not published |
| Branch | Package channel | npm dist-tag | Integrated Docker |
| --- | --- | --- | --- |
| `dev` | Development prerelease | `@dev` | None |
| `main` | Stable semantic release | `@latest` | Immutable tag on GitHub release; mutable aliases after manual promotion |