# Contributing ChannelGate (formerly Claude Gateway for Slack) is a self-hosted daemon that runs coding agents inside a container per conversation. Contributions are welcome, and because the gateway executes model output on someone's machine, review is strict about isolation, secret handling, and cross-platform behaviour. Everything below is what a reviewer will actually check. By contributing you accept the [Contributor License Agreement](CLA.md) — the `Signed-off-by` trailer on each commit *is* that acceptance (CLA Section 7). Participation is governed by the [Code of Conduct](CODE_OF_CONDUCT.md). ## Set up Requirements: Node **>= 22.13** (the floor is exact — `node:sqlite` is stable from 22.13 and the daemon refuses to boot below it), Git, and Linux — the daemon refuses to boot anywhere else. ```bash git clone && cd npm ci cp .env.example .env # full live setup also needs rootless Podman + provider API credentials npm start # admin UI + health endpoint on http://localhost:4747 ``` `.env` is gitignored and never committed. Runtime state (config, sessions, logs, the SQLite database) lives outside the repository under the gateway runtime root, not in the checkout. `INSTALL.md` covers a full working install; the tests below need neither Slack credentials nor a running daemon. ## Run the checks ```bash npm test # full node:test suite (test/*.test.js) npm run test:coverage # the same suite with the enforced coverage floors — what CI runs npm run check:static # syntax, undeclared identifiers, whitespace invariants npm run secret-scan # dependency-free credential scan over tracked files npm run check:dco -- origin/beta..HEAD # every development commit carries a sign-off trailer ``` `npm run test:security-coverage` enforces the extra coverage floors on the security-critical modules; run it when you touch authorization, confinement, secrets, or the admin API. A single file runs with `node --test test/.test.js`. Tests must leave no scratch directories behind. Create temp directories with `tempDir(prefix)` from `test/helpers.js` (never a bare `mkdtempSync(path.join(os.tmpdir(), …))`) — it registers them for an exit-handler cleanup, and `npm test` also runs `scripts/test-scratch-sweep.mjs` first to sweep anything a crashed run left in the system temp dir. Every one of these must pass before you open a pull request. CI runs the suite on Node 22.13 and 24 on ubuntu-latest. ## Branches and review Use your own fork, branch and isolated worktree. Start from the latest upstream `beta`, make focused commits and open a PR targeting `beta` with validation results. You do not need access to the publisher's GitHub account, private QA database or production host. Maintainers handle final integration and release. `beta` receives all completed development work. `main` is the stable update branch. A release PR from `beta` to `main` requires the full test and live acceptance evidence defined in `AGENTS.md`, followed by explicit user confirmation for that exact tested candidate. Beta work is not authorization to release; required checks that are blocked or unexecuted prevent promotion. Keep `FEATURES.md` and `TEST-PLAN.md` consistent with behavior. Include exact fixtures, setup, prompts/actions and pass rules for applicable Claude and Codex acceptance. Report live cases you could not execute explicitly so maintainers can run them; a local unit test is not a live pass. Never resolve a shared document conflict by discarding another contributor's evidence. Deployments that serve a checkout directly may require serialized landing using `npm run with-landing-lock -- `. Follow that host's operator instructions; ordinary contributors should not deploy or push directly to the upstream main branch. ## Commit rules - **Sign off every commit**: `git commit -s`. The `Signed-off-by: Your Name ` trailer is how you accept `CLA.md`; a real name and a reachable address are required, and pseudonymous sign-offs cannot be accepted. Already committed without it? `git rebase --signoff `. CI rejects the pull request otherwise (`scripts/check-dco.mjs`). - **Conventional, imperative subject**, present tense, no trailing period: `fix: reject an unsigned commit range`, not `fixed stuff`. - **One change per commit.** A refactor and a behaviour change are two commits. - **Stage only your own files** — `git add `, never `git add -A` / `git commit -a`. If the tree holds edits you did not make, leave them unstaged. - Explain *why* in the body. The diff already says what. ## What we accept Open a pull request directly for: - **Bug fixes** with a regression test that fails before the fix. - **Documentation** corrections and clarifications. - **Tests** covering existing behaviour, especially the security-critical modules. - **Platform adapters and engine runners** that follow the registry pattern described in `AGENTS.md`: a chat surface is an adapter plus a connector in `src/platforms/` with a complete capability descriptor; an engine is a runner plus an entry in `src/engines/registry.js`. Adding a `platform === "slack"` or `engine === "codex"` branch anywhere else is the thing those registries exist to prevent, and it will be sent back. **Ask first** — open an issue or a discussion before writing code — for: - new license tiers, limits, or anything touching license-key enforcement; - anything under `src/ee/`, which is **proprietary, source-visible** code owned by MAKEITFUTURE S.R.L. and is **not** under the Sustainable Use License — its own terms are in `src/ee/LICENSE-EE.md`, and `LICENSE.md` §3.2/§4.5 make removing, disabling, or circumventing the key verification, the usage limits, or the usage reporting a licence violation. We are happy to take bug reports and security findings about it; we cannot take patches to it without a prior discussion and a signed CLA covering that directory. The same applies to the enforcement call site in `src/gateway/run.js` (`licenseAdmission()`) and to the `license_usage` schema; - licensing, CLA, or trademark text (`LICENSE.md`, `CLA.md`, `TRADEMARK.md`, `docs/LICENSING-*`); - new runtime dependencies — the project is deliberately close to dependency-free outside the Slack, MCP, and Composio SDKs; - schema changes (migrations are append-only; an existing migration is never edited); - anything that changes the security model. ## Where design notes go Put a short design note in the pull request description: the problem, the option you chose, the options you rejected, and how it is tested. That is the durable record for a reviewer. Larger design documents, roadmaps, and audit notes are kept privately by the maintainers and are not part of this repository — do not add a `docs/plans/` or a working-notes file to a pull request. Behaviour changes update `TEST-PLAN.md` (the cumulative regression list) and, when they ship a user-visible capability, `FEATURES.md` and `CHANGELOG.md`. ## Security issues Do not open a public issue, discussion, or pull request for a suspected vulnerability. Report it privately per [SECURITY.md](SECURITY.md) — `contact@makeitfuture.com`, with the affected version, reproduction, and impact, and no live credentials or customer data. Rotate anything that may have leaked before you report. ## The rules reviewers enforce These are not style preferences; a change that breaks one of them does not merge. - **Confinement is the product, and the container is the boundary.** Every turn runs inside the channel's own container: a per-channel HOME volume, only the work folder (plus its clean workspace and artifact dir) mounted, no host home or gateway root by default. The explicit Full-access whole-home option widens this boundary and must remain off unless deliberately selected by the operator. Every channel folder still gets the lockdown settings — automatic memory off, curated permission allowlist, the MCP allowlist — as policy, never a `sandbox` block. An engine is never exec'd outside a container, and nothing a run can do changes what its container mounts. The boundary is filesystem and process isolation, not egress: *Allow network* is a per-channel switch the engines are told about, not a filter. - **Secrets never ride a listing response.** Listing endpoints return `has*` and `last4` only. A value is fetched one at a time through the reveal endpoint, which re-checks the admin password and audit-logs what was revealed, never the value. New secret fields go in the reveal allowlist. Nothing is hardcoded and nothing is logged. - **Linux only.** ChannelGate targets Linux with systemd and rootless Podman: no macOS/launchd branches, no BSD-tool assumptions — and still no `setsid` or other platform-specific binaries; prefer portable Node APIs over shelling out and keep shell POSIX-portable. - **A run is never killed for being quiet**, and a waiting state always announces itself. Silence that looks like death is the bug the heartbeat and watchdog exist to prevent. - **Platform capabilities are declared once and fail closed.** Read them through the capability helpers; an undeclared capability resolves to the least capable value. - **Only admins get skipped permissions**, and authorization is checked before anything else runs. ## Questions Usage and troubleshooting: [SUPPORT.md](SUPPORT.md). Licensing, commercial use, and partner inquiries: `contact@makeitfuture.com` and [`docs/LICENSING-FAQ.md`](docs/LICENSING-FAQ.md).