diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 08c0715..ecf53e7 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -1,5 +1,5 @@ --- -description: Cut a new rss2msg release — derive the version, commit the changelog to main, then tag and push to trigger the release pipeline. +description: Cut a new rss2msg release — derive the version, roll the BUSL Change Date, commit the changelog to main, then tag and push to trigger the release pipeline. argument-hint: "[version] e.g. v1.0.0 (optional; derived from commits if omitted)" --- @@ -11,5 +11,5 @@ derive it from the commit history with `git cliff --bumped-version`): $ARGUMENTS Invoke the `cut-release` skill and follow it exactly. Run on `main` (not a worktree), -stage only `CHANGELOG.md`, and **confirm with me before pushing the tag** — that push +stage only `CHANGELOG.md` and `LICENSE`, and **confirm with me before pushing the tag** — that push triggers the public release pipeline. diff --git a/.claude/skills/cut-release/SKILL.md b/.claude/skills/cut-release/SKILL.md index 13149a5..9e383d0 100644 --- a/.claude/skills/cut-release/SKILL.md +++ b/.claude/skills/cut-release/SKILL.md @@ -1,6 +1,6 @@ --- name: cut-release -description: Use when cutting a new rss2msg release / version — derives the next semver from Conventional Commits with git-cliff, commits the regenerated CHANGELOG.md to main, then tags and pushes to trigger the GoReleaser pipeline. Triggers on "cut a release", "new version", "release vX.Y.Z", "/release". +description: Use when cutting a new rss2msg release / version — derives the next semver from Conventional Commits with git-cliff, rolls the BUSL Change Date in LICENSE, commits both to main, then tags and pushes to trigger the GoReleaser pipeline. Triggers on "cut a release", "new version", "release vX.Y.Z", "/release". --- # Cutting a new rss2msg release @@ -10,10 +10,12 @@ rss2msg ships through a **tag-driven** pipeline: pushing a `vX.Y.Z` tag triggers multi-platform binaries, Linux packages, a multi-arch Docker image, the Homebrew cask, and the GitHub Release. The version comes entirely from the pushed tag. -This skill owns the changelog locally: it regenerates and **commits `CHANGELOG.md` to -`main` before tagging**, so the tagged commit already carries the populated changelog -that GoReleaser bundles into the artifacts. The workflow does **not** regenerate or sync -the changelog — that is this skill's job. +This skill owns two per-release files locally: it regenerates and **commits +`CHANGELOG.md` to `main` before tagging**, so the tagged commit already carries the +populated changelog that GoReleaser bundles into the artifacts, and it **rolls the BUSL +`Change Date` in `LICENSE`** so each version converts to Apache-2.0 four years after its +own release. The workflow does **not** regenerate or sync either file — that is this +skill's job. See [docs/development/releasing.md](../../../docs/development/releasing.md) for the full pipeline reference. @@ -32,12 +34,12 @@ copy. ## Hard rules - **Never `git add -A` / `git add .`** — this repo is an Obsidian vault with - auto-staging. Stage **only** `CHANGELOG.md` with an explicit pathspec. + auto-staging. Stage **only** `CHANGELOG.md` and `LICENSE` with explicit pathspecs. - **Never** `--no-verify` or bypass hooks. - **Confirm with the user before pushing the tag** — that push triggers the public release. Pushing the changelog commit to `main` happens first and does not trigger the release (only a tag push does). -- **Never put `[skip ci]` in the changelog commit message.** Step 5 tags that exact +- **Never put `[skip ci]` in the changelog commit message.** Step 6 tags that exact commit, and GitHub honors `[skip ci]` on a tag push's HEAD commit — so a `[skip ci]` changelog commit silently **skips the release workflow**. (This bit v0.3.0.) - **Abort** on any failed preflight check and report exactly what failed; do not paper over it. @@ -92,14 +94,33 @@ Get explicit go-ahead on the version before changing any files. If the commits s last tag are thin or unconventional (git-cliff skips non-Conventional and merge commits), point that out. -### 4. Regenerate and commit CHANGELOG.md to main +### 4. Roll the BUSL Change Date in LICENSE -Regenerate the full changelog with the new tag label, then stage **only** that file: +rss2msg ships under the Business Source License 1.1, and the Change Date is a +**per-version** value — BUSL applies "separately for each version". Each release must +carry a Change Date four years out (the BUSL ceiling), or that version's +source-available window silently shrinks. + +```bash +sed -i "s/^Change Date:.*/Change Date: $(date -u -d '+4 years' +%Y-%m-%d)/" LICENSE +grep '^Change Date:' LICENSE # verify: four years from today, YYYY-MM-DD +git diff --stat LICENSE # verify: exactly one line changed +``` + +- Change **only** the `Change Date:` line. If `git diff` shows anything else touched, + revert and stop — the rest of `LICENSE` is legal text and is never edited by this skill. +- The value is always today + 4 years. Do not carry the previous release's date forward, + and do not invent a different horizon. + +### 5. Regenerate and commit CHANGELOG.md to main + +Regenerate the full changelog with the new tag label, then stage **only** the changelog +and the license bump from step 4: ```bash git cliff --config cliff.toml --tag "" --output CHANGELOG.md -git add CHANGELOG.md # explicit pathspec — nothing else -git status # verify ONLY CHANGELOG.md is staged +git add CHANGELOG.md LICENSE # explicit pathspecs — nothing else +git status # verify ONLY these two files are staged git commit -m "chore(release): update CHANGELOG.md for " git push origin main ``` @@ -107,13 +128,13 @@ git push origin main Notes: - The `chore(release):` prefix matters: `cliff.toml` skips `^chore\(release\):` commits, so this commit never pollutes a future changelog. -- **Do not append `[skip ci]`.** Step 5 tags this same commit, and GitHub skips the +- **Do not append `[skip ci]`.** Step 6 tags this same commit, and GitHub skips the tag-triggered release workflow when its HEAD commit message contains `[skip ci]` — which is exactly how the v0.3.0 release got silently skipped. The cost of omitting it is one redundant CI run on `main`, which is acceptable; a skipped release is not. -- Verify the staged set is **only** `CHANGELOG.md` before committing (auto-staging hazard). +- Verify the staged set is **only** `CHANGELOG.md` and `LICENSE` before committing (auto-staging hazard). -### 5. Tag, confirm, and push +### 6. Tag, confirm, and push ```bash git tag "" # tags the changelog commit you just pushed @@ -126,7 +147,7 @@ release. On confirmation: git push origin "" ``` -### 6. Report +### 7. Report Print the link to the running release workflow so the user can watch it: @@ -143,7 +164,7 @@ Homebrew cask for non-prerelease tags, and the GitHub Release). - **Prerelease tags** (`-rc.1`, etc.): valid and supported. GoReleaser skips the Homebrew cask for prereleases automatically (`skip_upload: auto`). - **`v0.0.*` tags** are ignored by git-cliff (`skip_tags`), so don't use them. -- If the `git push origin main` in step 4 is rejected because `main` is branch-protected +- If the `git push origin main` in step 5 is rejected because `main` is branch-protected against direct pushes, stop — the changelog must reach `main` before the tag. Surface this to the maintainer rather than forcing it. - **Release skipped (no Release run after the tag push)?** The usual cause is `[skip ci]` diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 0000000..c8a9d55 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,56 @@ +# Asks first-time contributors to sign the CLA (CLA.md) before their PR can be merged, +# and records signatures in the `cla-signatures` branch of this repository. +# +# Setup required once, by a maintainer, before this workflow can pass: +# 1. Create a classic PAT with `repo` scope (or a fine-grained PAT with Contents: +# read/write on this repository) and add it as the `CLA_SIGNATURES_TOKEN` secret. +# The action needs it to commit the signature file; the default GITHUB_TOKEN is not +# enough because the write happens outside the PR's ref. +# 2. Add a branch-protection / ruleset requirement on `main` for the "CLA Assistant" +# status check, so an unsigned PR cannot be merged. +# +# `pull_request_target` is required so the workflow can comment and set a status on pull +# requests from forks. This job never checks out or executes PR code — it only reads the +# event payload and comment text — so the usual pull_request_target hazard does not apply. +name: CLA + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +jobs: + cla: + name: CLA Assistant + runs-on: ubuntu-latest + steps: + - name: Check or record CLA signature + if: >- + github.event_name == 'pull_request_target' || + github.event.comment.body == 'recheck' || + github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA' + uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_SIGNATURES_TOKEN }} + with: + path-to-document: https://github.com/IAmBod/rss2msg/blob/main/CLA.md + path-to-signatures: signatures/version1/cla.json + branch: cla-signatures + # Maintainer and bots never need to sign. + allowlist: IAmBod,dependabot[bot],github-actions[bot] + custom-notsigned-prcomment: >- + Thanks for the pull request! Before it can be merged, please read the + [Contributor License Agreement](https://github.com/IAmBod/rss2msg/blob/main/CLA.md) + and sign it by posting the comment below. rss2msg is source-available under the + Business Source License 1.1, and the CLA is what lets the project offer your + contribution under those terms — you keep your copyright. + custom-allsigned-prcomment: >- + All contributors have signed the CLA. Thank you! diff --git a/CLA.md b/CLA.md new file mode 100644 index 0000000..422efc2 --- /dev/null +++ b/CLA.md @@ -0,0 +1,118 @@ +# rss2msg Contributor License Agreement + +Thank you for your interest in contributing to rss2msg (the "Project"), maintained by +Bödei Károly (the "Maintainer"). + +This Contributor License Agreement ("Agreement") documents the rights you grant to the +Maintainer when you contribute. It is adapted from the Apache Software Foundation +Individual Contributor License Agreement v2.0. + +**Why this exists.** rss2msg is distributed under the +[Business Source License 1.1](LICENSE) — a source-available license under which the +Maintainer also offers separate commercial licenses. For the Project to be offered under +those terms, and for each version to convert to the Apache License 2.0 on its Change +Date, the Maintainer needs a clear written grant of rights covering every contribution. +Without one, contributed code could only ever be offered under the exact terms it +arrived under. + +**You keep your copyright.** This Agreement is a license, not a transfer of ownership. + +## 1. Definitions + +**"You"** means the individual or legal entity agreeing to this Agreement. + +**"Contribution"** means any original work of authorship, including any modifications or +additions to an existing work, that is intentionally submitted by You to the Maintainer +for inclusion in, or documentation of, the Project. "Submitted" means any form of +electronic, verbal, or written communication sent to the Maintainer or its +representatives — including pull requests, issues, and electronic mailing lists — but +excluding any communication conspicuously marked or otherwise designated in writing by +You as "Not a Contribution". + +## 2. Grant of copyright license + +Subject to the terms of this Agreement, You grant to the Maintainer and to recipients of +software distributed by the Maintainer a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, +publicly display, publicly perform, sublicense, and distribute Your Contributions and +such derivative works. + +## 3. Grant of patent license + +Subject to the terms of this Agreement, You grant to the Maintainer and to recipients of +software distributed by the Maintainer a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Project. This license +applies only to those patent claims licensable by You that are necessarily infringed by +Your Contribution alone, or by combination of Your Contribution with the Project. + +If any entity institutes patent litigation against You or any other entity alleging that +Your Contribution, or the Project to which You contributed, constitutes direct or +contributory patent infringement, then any patent licenses granted to that entity under +this Agreement for that Contribution or Project terminate as of the date such litigation +is filed. + +## 4. Outbound licensing + +You acknowledge and agree that the Maintainer may license the Project, including Your +Contributions, under any terms the Maintainer chooses. This expressly includes: + +- the Business Source License 1.1, and any successor source-available license; +- the Apache License 2.0, to which each version converts on its Change Date; +- separate commercial or proprietary licenses, including licenses permitting the + operation of a hosted or managed service based on the Project. + +The Maintainer is under no obligation to include Your Contribution in the Project or in +any distribution of it. + +## 5. Your representations + +You represent that: + +1. You are legally entitled to grant the licenses above. +2. Each of Your Contributions is Your original creation, or You otherwise have the right + to submit it under this Agreement. +3. If your employer has rights to intellectual property that You create, You have + received permission to make Contributions on behalf of that employer, your employer + has waived such rights, or your employer has executed a separate Corporate CLA with + the Maintainer. + +If You wish to submit work that is not Your original creation, submit it separately from +any Contribution, identifying the complete details of its source and of any license or +other restriction you are personally aware of, and conspicuously mark the work as +"Submitted on behalf of a third party: [named here]". + +## 6. No warranty, no support obligation + +You are not expected to provide support for Your Contributions, except to the extent You +wish to. Unless required by applicable law or agreed to in writing, You provide Your +Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +express or implied, including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +## 7. Notification + +You agree to notify the Maintainer of any facts or circumstances you become aware of that +would make these representations inaccurate in any respect. + +## How to sign + +Signing is automated — there is nothing to print, scan, or email. + +When you open your first pull request, a bot comments with a link to this document. Reply +on that pull request with exactly: + +``` +I have read the CLA Document and I hereby sign the CLA +``` + +Your signature is recorded against your GitHub username in the `cla-signatures` branch of +this repository. You will not be asked again on later pull requests. + +**Contributing on behalf of a company?** Email for a Corporate CLA +before submitting. + +## Related + +- [LICENSE](LICENSE) — the Business Source License 1.1 terms the Project ships under. +- [CONTRIBUTING.md](CONTRIBUTING.md) — how to get a change reviewed and merged. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 415ed76..34e0b78 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,6 +32,7 @@ places: or a coordinator backend (requires Docker), and `bash scripts/check-doc-links.sh` if you touched `docs/` or the `README`. 5. **Open a pull request** against `main` and fill in the template. +6. **Sign the CLA** — a bot asks on your first PR; see [below](#contributor-license-agreement). ## Code of Conduct @@ -45,6 +46,25 @@ Please do **not** open public issues for security vulnerabilities — see ## License -rss2msg is distributed under the [Business Source License 1.1](LICENSE). By -contributing, you agree that your contributions are licensed under the same terms -as the rest of the project. +rss2msg is **source-available**, not open source: it is distributed under the +[Business Source License 1.1](LICENSE). You may use, modify, and redistribute it, +including commercially — the one carve-out is offering rss2msg to third parties as a +hosted or managed "Feed-to-Message Service", which needs a separate commercial license. +Each version converts to the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) +on its Change Date. See [`LICENSE`](LICENSE) for the exact terms. + +## Contributor License Agreement + +Before your first pull request can be merged, you need to sign the +[Contributor License Agreement](CLA.md). It is adapted from the Apache Software +Foundation's Individual CLA. + +**You keep your copyright** — the CLA is a license grant, not a transfer. What it adds +is permission for the project to offer your contribution under the terms above, +including the separate commercial licenses and the eventual Apache-2.0 conversion. +Without it, contributed code could only be offered under the exact terms it arrived +under, which would fracture the licensing of the codebase. + +Signing is automated: open a PR, and a bot comments with a link to the document and the +one-line comment to reply with. You are only asked once. Contributing on behalf of a +company? Email for a Corporate CLA first. diff --git a/README.md b/README.md index c9153a4..f1e902f 100644 --- a/README.md +++ b/README.md @@ -39,13 +39,24 @@ Full documentation lives in [`docs/`](./docs/index.md). Start there, or jump in: --- +## Contributing + +Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the workflow +and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for the ground rules. Please report +security issues privately per [SECURITY.md](./SECURITY.md) rather than as public issues. + +Before a first pull request is merged, contributors sign the +[Contributor License Agreement](./CLA.md) — a bot handles it, and you keep your +copyright. + ## License -rss2msg is licensed under the [Business Source License 1.1](./LICENSE). +rss2msg is **source-available**, not open source. It is distributed under the +[Business Source License 1.1](./LICENSE). You may use, modify, and redistribute it freely, **including for commercial purposes** — the one exception is that you may not offer rss2msg to third parties as a hosted or managed "Feed-to-Message Service" without a separate commercial license. Each released version converts to the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) -on its Change Date (four years after release). See [`LICENSE`](./LICENSE) for the -exact terms; for commercial-licensing questions, contact info@iambod.dev. +on its Change Date, four years after that version is released. See [`LICENSE`](./LICENSE) +for the exact terms; for commercial-licensing questions, contact info@iambod.dev. diff --git a/docs/development/contributing.md b/docs/development/contributing.md index a125e50..8604fde 100644 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -2,8 +2,8 @@ title: Contributing type: how-to tags: [rss2msg/docs, development] -summary: Branch, commit, test, and pull-request conventions used in this repository. -updated: 2026-05-31 +summary: Branch, commit, test, and pull-request conventions used in this repository, plus the license terms and the Contributor License Agreement. +updated: 2026-07-21 --- # Contributing @@ -26,6 +26,8 @@ changes get made. Run `task test-integration` too if your change touches a sink, the state store, or a coordinator backend (requires Docker). 4. Open a pull request against `main`. +5. Sign the [Contributor License Agreement](#licensing-and-the-cla) — a bot asks on + your first pull request. ## Commit messages @@ -52,8 +54,31 @@ Docs live under [`docs/`](../index.md) and follow a Diátaxis structure `updated`) and a `## Related` footer. - Run `bash scripts/check-doc-links.sh` and confirm it prints `OK` before committing. +## Licensing and the CLA + +rss2msg is **source-available, not open source**. It ships under the +[Business Source License 1.1](../../LICENSE): use, modification, and redistribution are +permitted, including commercially, with one carve-out — offering rss2msg to third +parties as a hosted or managed "Feed-to-Message Service" requires a separate commercial +license from the maintainer. Each version converts to the Apache License 2.0 on its +Change Date, four years after that version is released (see +[Releasing](releasing.md#change-date)). + +Because of that model, contributions are covered by a +[Contributor License Agreement](../../CLA.md), adapted from the Apache Software +Foundation's Individual CLA. You keep your copyright; the CLA grants the maintainer the +right to offer your contribution under the licenses above, including the separate +commercial licenses and the eventual Apache-2.0 conversion. + +Signing is automated by [`.github/workflows/cla.yml`](../../.github/workflows/cla.yml): +open a pull request, and a bot comments with a link to the document and the exact reply +that records your signature. You are only asked once — signatures live in the +`cla-signatures` branch. Contributing on behalf of a company? Email + for a Corporate CLA before submitting. + ## Related - [Building and Testing](building-and-testing.md) — the commands the checks run. - [Project Layout](project-layout.md) — where to make a given change. - [Releasing](releasing.md) — how conventional commits become tagged releases and a changelog. +- [CLA.md](../../CLA.md) — the Contributor License Agreement itself. diff --git a/docs/development/releasing.md b/docs/development/releasing.md index 081ce5a..36e6599 100644 --- a/docs/development/releasing.md +++ b/docs/development/releasing.md @@ -3,7 +3,7 @@ title: Releasing type: how-to tags: [rss2msg/docs, development, release] summary: The release pipeline — golangci-lint in CI, git-cliff for the changelog and version bumps, and GoReleaser for multi-platform binaries, Linux packages (.deb/.rpm/.apk), a multi-arch Docker image, and a Homebrew formula, all driven by a semver tag. -updated: 2026-06-16 +updated: 2026-07-21 --- # Releasing @@ -50,17 +50,27 @@ confirmed tag push). To do it by hand: ```bash git cliff --bumped-version # e.g. prints v0.3.0 ``` -3. Regenerate `CHANGELOG.md` for the new version and commit it to `main`. This commit - carries the populated changelog that GoReleaser bundles into the release artifacts: +3. Roll the [Change Date](#change-date) in `LICENSE` forward to four years from today — + the BUSL maximum — so this version gets its full protection window: + ```bash + sed -i "s/^Change Date:.*/Change Date: $(date -u -d '+4 years' +%Y-%m-%d)/" LICENSE + grep '^Change Date:' LICENSE # verify + ``` +4. Regenerate `CHANGELOG.md` for the new version and commit it to `main` together with + the `LICENSE` bump. This commit carries the populated changelog that GoReleaser + bundles into the release artifacts: ```bash git cliff --config cliff.toml --tag v0.3.0 --output CHANGELOG.md - git add CHANGELOG.md # explicit pathspec only - git commit -m "chore(release): update CHANGELOG.md for v0.3.0 [skip ci]" + git add CHANGELOG.md LICENSE # explicit pathspecs only + git commit -m "chore(release): update CHANGELOG.md for v0.3.0" git push origin main ``` `cliff.toml` skips `chore(release):` commits, so this never pollutes a future - changelog, and `[skip ci]` avoids a redundant CI run. -4. Tag that commit and push. The tag is what triggers the release workflow: + changelog. **Do not add `[skip ci]`** — the next step tags this exact commit, and + GitHub honors `[skip ci]` on a tag push's HEAD commit, silently skipping the release + workflow. (This is what happened to v0.3.0.) One redundant CI run is the cheaper + outcome. +5. Tag that commit and push. The tag is what triggers the release workflow: ```bash git tag v0.3.0 git push origin v0.3.0 @@ -89,6 +99,31 @@ The release workflow then: see [`homebrew_casks:` in `.goreleaser.yaml`](../../.goreleaser.yaml)); - publishes a GitHub Release whose notes are the git-cliff section for that tag. +## Change Date + +rss2msg ships under the [Business Source License 1.1](../../LICENSE). Each version stays +source-available until its **Change Date**, then converts to the Apache License 2.0. +BUSL applies "separately for each version", and the Change Date "may vary for each +version" — so the date is a **per-release** value, not a repository-wide constant. + +That is why step 3 of [Cut a release](#cut-a-release) rolls `Change Date:` in `LICENSE` +forward to four years from the release day, and stages `LICENSE` alongside `CHANGELOG.md`. +Because the tag captures the tree, every tag — and every archive and package GoReleaser +builds from it — carries the Change Date that belongs to that version: + +| Tagged | `Change Date` in that tag's `LICENSE` | Converts to Apache-2.0 | +| --- | --- | --- | +| v0.4.0 on 2026-07-21 | 2030-07-21 | 2030-07-21 | +| v0.5.0 on 2027-03-02 | 2031-03-02 | 2031-03-02 | + +Four years is the ceiling, not a free parameter: BUSL converts on the Change Date **or** +"the fourth anniversary of the first publicly available distribution of a specific +version … whichever comes first". A later date in the file would have no effect. + +Skipping this step is what makes the window shrink — leave the date fixed and a version +released three years from now would convert after one year instead of four. Leave it in +the past and the release ships already converted. + ## Version metadata The binary's build metadata comes from `-ldflags -X main.version=… -X main.commit=…