CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# 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 <your fork> && cd <checkout>
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/<name>.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 -- <command>`. 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 <you@example.com>`
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
<base>`. 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 <path>`, 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).