diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f4fb3f7 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +extend-ignore = E203, W503 +exclude = .git, __pycache__, */migrations/* diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c7e2995..168fd56 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,7 +13,10 @@ - [ ] Targets Odoo 18.0 and follows the existing code style - [ ] Added/updated tests under `tests/` (tagged `codexoo`) and they pass locally -- [ ] Updated `CHANGELOG.md` +- [ ] Updated `CHANGELOG.md` (bullet under `## [Unreleased]`) +- [ ] Updated `SECURITY.md` if this touches a safety-critical path (else N/A) +- [ ] All commits are signed off (`git commit -s` → DCO) +- [ ] I am the author and have reviewed/understand **all** code here, including any AI-assisted parts, and it is mine to license - [ ] Tool endpoints still run **as the user, never superuser** - [ ] Did not alter Codex engine references (ChatGPT/device-code login, `CODEX_HOME`, CLI glob, `codex_thread_id` / `codex_item_id`) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..f5daf97 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,103 @@ +name: PR checks + +# Enforced contribution gates for community pull requests. Each job below is a +# separate status check; mark them "required" in branch protection to block merge. +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + +jobs: + changelog: + name: changelog + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Require a CHANGELOG.md update + if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-changelog') }} + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" + if git diff --name-only "origin/${{ github.base_ref }}...HEAD" | grep -qx 'CHANGELOG.md'; then + echo "CHANGELOG.md updated ✓" + else + echo "::error::This PR must update CHANGELOG.md (add a bullet under '## [Unreleased]'). A maintainer can apply the 'skip-changelog' label for trivial PRs." + exit 1 + fi + + dco: + name: dco + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Verify Signed-off-by on every commit + run: | + set -euo pipefail + git fetch --no-tags origin "${{ github.base_ref }}" + fail=0 + for sha in $(git rev-list "origin/${{ github.base_ref }}..HEAD"); do + if ! git log -1 --format='%(trailers:key=Signed-off-by)' "$sha" | grep -qiE '<.+@.+>'; then + subject=$(git log -1 --format='%s' "$sha") + echo "::error::Commit $sha (\"$subject\") is missing a Signed-off-by line." + fail=1 + fi + done + if [ "$fail" -ne 0 ]; then + echo "Fix with: git commit -s --amend (single commit)" + echo " or: git rebase --signoff origin/${{ github.base_ref }} (several)" + exit 1 + fi + echo "All commits signed off ✓" + + security-doc: + name: security-doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Require SECURITY.md when touching safety-critical paths + if: ${{ !contains(github.event.pull_request.labels.*.name, 'security-reviewed') }} + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" + changed=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD") + sensitive=$(printf '%s\n' "$changed" | grep -E '(/controllers/(tools|main)\.py$|/models/ir_http\.py$|/models/[^/]*_session\.py$|/models/[^/]*_tool\.py$|/bridge/|/security/)' || true) + if [ -n "$sensitive" ]; then + echo "Safety-critical files changed:"; printf '%s\n' "$sensitive" + if printf '%s\n' "$changed" | grep -qx 'SECURITY.md'; then + echo "SECURITY.md updated ✓" + else + echo "::error::This PR changes safety-critical files but does not update SECURITY.md. Update SECURITY.md to reflect the change, or have a maintainer add the 'security-reviewed' label." + exit 1 + fi + else + echo "No safety-critical paths touched ✓" + fi + + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: pip install flake8 + - name: flake8 on changed Python files + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" + files=$(git diff --name-only --diff-filter=ACMR "origin/${{ github.base_ref }}...HEAD" \ + | grep -E '\.py$' | grep -vE '(/migrations/|__pycache__)' || true) + if [ -z "$files" ]; then echo "No Python files changed ✓"; exit 0; fi + echo "Linting:"; printf '%s\n' "$files" + printf '%s\n' "$files" | xargs flake8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c8f40..5f70ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ All notable changes to **Codexoo** are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses -Odoo-style versioning (`.MAJOR.MINOR.PATCH`, e.g. `18.0.1.0.0`). +Odoo-style versioning (`.MAJOR.MINOR.PATCH`, e.g. `18.0.1.0.0`). Add new +entries under `## [Unreleased]`; a maintainer assigns the version at release time. + +## [Unreleased] + +### Added +- **Contribution gates** — a `pr-checks` CI workflow (CHANGELOG, DCO sign-off, + SECURITY.md-when-relevant, flake8) plus branch protection, and `CONTRIBUTING.md` + guidance covering AI-assisted contributions and the Developer Certificate of + Origin (`DCO`). ## [18.0.1.0.1] — 2026-06-11 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fa0964..0ec72d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,9 +33,65 @@ welcome — bug reports, documentation, tests, and code. ## Pull requests -- One logical change per PR; write a clear description and link any issue. -- Make sure CI is green (lint + tests). -- Update `CHANGELOG.md` under an *Unreleased* heading. +Open one logical change per PR with a clear description; link any issue +(`Closes #123`). The items below are **enforced by CI and branch protection** — the +merge button stays disabled until they pass: + +- **Changelog.** Add a bullet under the `## [Unreleased]` heading in + [`CHANGELOG.md`](CHANGELOG.md). (A maintainer can apply the `skip-changelog` label + for genuinely trivial PRs.) +- **Security docs.** If your PR touches a safety-critical path — + `controllers/tools.py`, `controllers/main.py`, `models/ir_http.py`, + `models/*_session.py`, `models/*_tool.py`, `bridge/`, or `security/` — update + [`SECURITY.md`](SECURITY.md) to reflect the change (or have a maintainer apply the + `security-reviewed` label). +- **Sign-off.** Every commit must be signed off — see *Developer Certificate of + Origin* below. +- **Lint.** `flake8` must pass on the Python files you changed. +- **Review.** At least one maintainer approval. + +There is no test job in CI, so run the suite locally before pushing: + +```bash +odoo-bin ... -i codexoo --test-tags codexoo --stop-after-init +``` + +## AI-assisted contributions + +AI coding tools are welcome here — this project is itself an AI assistant. The bar is +simply the same as for any other contribution: + +- **You are the author.** You are fully accountable for every line, AI-generated or + not. Only open PRs you understand and can explain; reviewers may ask how a change + works. +- **Provenance.** AI can emit third-party or copyleft code verbatim. Submit only code + that is your original work, or that you otherwise have the right to license under + the terms in *Licensing of contributions* below. If you can't vouch for a snippet's + origin, don't include it. +- **Never let AI weaken the security model.** Re-read [`SECURITY.md`](SECURITY.md). + Changes that introduce `sudo()` / superuser execution, broaden the SQL tool past + read-only `SELECT`, bypass `ir.model.access` or record rules, widen the bridge + token's scope, or re-enable denied CLI built-ins **will be rejected** — AI + assistants suggest these "helpfully" all the time. Keep the invariants and ship + tests that prove them. +- **Verify.** Run the tests locally; never paste credentials, customer data, or + proprietary code into an AI tool while working on this project. +- **Quality over volume.** Low-effort or bulk AI-generated PRs that don't meet the + bar may be closed without a detailed review. + +## Developer Certificate of Origin + +Contributions are accepted under the **Developer Certificate of Origin 1.1** (full +text in the [`DCO`](DCO) file). Sign off every commit: + +```bash +git commit -s -m "your message" +``` + +This appends a `Signed-off-by: Your Name ` trailer. Signing off +certifies the DCO **and**, for this project, that any AI-assisted code in the commit +is yours to license under the terms below. CI rejects unsigned commits; fix them with +`git commit -s --amend` (one commit) or `git rebase --signoff origin/18.0` (several). ### Licensing of contributions (important) diff --git a/DCO b/DCO new file mode 100644 index 0000000..49b8cb0 --- /dev/null +++ b/DCO @@ -0,0 +1,34 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/README.md b/README.md index ce9e6bf..4b54ffe 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ superuser, with a full audit trail.** [![License: LGPL-3.0](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](LICENSE) [![Odoo](https://img.shields.io/badge/Odoo-18.0-714B67.svg)](https://www.odoo.com) +[![PR checks](https://github.com/cicdoo/codexoo/actions/workflows/pr-checks.yml/badge.svg)](https://github.com/cicdoo/codexoo/actions/workflows/pr-checks.yml) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [Quick start](#-quick-start) · [Security model](#-security-model) ·