Contributing¶
Thanks for your interest! This project welcomes focused contributions. Before you open a PR, please read the policies below — they keep the review cycle short.
Setup¶
git clone <repo-url>
cd XFerryServer
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,lint,test]"
pre-commit install # recommended: runs ruff/mypy/yaml checks on commit
On Windows PowerShell, activate the environment with
.venv\Scripts\Activate.ps1 and use the same pip install command.
To reproduce the pinned CI/docs/security toolchain locally, use the shared
constraints file. This also pins the pre-commit executable installed by the
dev extra:
constraints/ci.txt is the only committed dependency authority for CI,
Docker, security, docs, and reproducible local installs. Local uv.lock files
are intentionally ignored; if you use uv locally, regenerate its lock from the
current pyproject.toml and constraints file instead of committing it.
Supported Python versions are 3.10 through 3.14. Package metadata caps the
range below 3.15. CI tests Python 3.14 in the constrained matrix and runs a
dedicated readiness job for pip check, import smoke, wheel smoke, and
pip-audit with constraints/ci.txt.
In Windows PowerShell, set the constraint as an environment variable:
$env:PIP_CONSTRAINT = "constraints/ci.txt"
pip install -e ".[dev,lint,test]"
Remove-Item Env:PIP_CONSTRAINT
Branching¶
- Work from
mainon a feature branch:feature/<short-slug>,fix/<issue>,security/<id>,docs/<area>. mainis protected. All changes land via PR with a passing CI.- Rebase, do not merge, when updating your branch from
main(git pull --rebase origin main).
Commit messages¶
Use Conventional Commits:
Types we use:
| Type | When to use |
|---|---|
feat |
New user-facing capability |
fix |
Bug fix — include reproducer in the body |
security |
Security-impacting change — link advisory ID |
perf |
Measurable performance improvement |
refactor |
Internal change, no behavior shift |
test |
Tests only, no production change |
docs |
Documentation only |
ci |
CI/workflow/tooling change |
deps |
Dependency bump (often by Dependabot) |
chore |
Anything else that doesn't change behavior |
Breaking changes get a ! after the type (feat!:) and a BREAKING CHANGE:
footer.
Running checks locally¶
The CI (.github/workflows/ci.yml) runs all of these — run them locally
first to avoid round-trips.
When you need to reproduce CI package resolution exactly, prefix the install
step with PIP_CONSTRAINT=constraints/ci.txt.
Browser smoke is release-gating in CI. Its @playwright/cli package is pinned
in .github/workflows/ci.yml and tools/browser_smoke.py; update those pins
together when refreshing the smoke toolchain.
pre-commit run --all-files
python -m pip check
python -c "import acme, cryptography, josepy; from OpenSSL import SSL"
python tools/check_dependency_constraints.py --constraints constraints/ci.txt
ruff check xferry src tests
ruff format --check xferry src tests
mypy xferry src
pytest --cov=src --cov-report=term-missing
python tools/sync_docs.py --check
python tools/check_stale_docs.py
python tools/browser_smoke.py --mode first-run
python tools/browser_smoke.py --mode ui-contracts
python tools/browser_smoke.py --mode full
# optional when docs extras are installed
mkdocs build --strict
Coverage gate in CI is 65 %; aim higher.
The independently runnable browser modes are first-run, ui-contracts,
request-matrix, advanced, files, notepad, mobile, and aggregate
full. To exercise the exact first-run assertions against an already running
source, installed-wheel, or image server, use:
This external-target path starts no additional server. Results and failure
diagnostics default to output/playwright/browser-smoke/<mode>; pass
--artifacts-dir to choose a CI or local artifact directory.
Release artifacts¶
The Release Artifacts workflow runs on v* tags and manual
workflow_dispatch. Neither the public xferry PyPI distribution nor the
expected ghcr.io/kgmnotes/xferry release image exists yet; their first
coordinated publication is intentionally deferred. Manual runs remain
verification-only and never publish to either registry.
The managed VPS installer and latest/download release assets have not been
published, and this documentation change does not publish, tag, deploy,
or mutate a VPS. Do not describe the two-command installer URL as live until a
verified release has actually published its manifest and platform binary.
Publication is downstream of one shared gate. The build job creates and
attests dist/*.whl and dist/*.tar.gz, installs the wheel in a fresh virtual
environment outside the checkout, runs CLI/import and static-asset probes, and
executes both the shared external-target first-run and the installed-package
full browser aggregate. In parallel, image-verify builds the hardened local
image and runs its lifecycle/browser verification. release-gate succeeds only
after both verification jobs.
For a push event on a v* tag, and only after release-gate,
publish-pypi and publish-ghcr become eligible to run independently in
parallel. A verification failure therefore prevents both registry mutations.
Manual runs stop at the gate, even when dispatched against a tag. Browser
diagnostics are uploaded with if: always().
The workflow uses explicit permissions: contents: read for checkout,
packages: write for GHCR publication, id-token: write for PyPI Trusted
Publishing/OIDC, and attestations: write for GitHub artifact attestations. It
also scans the pinned constraints/ci.txt dependency set with pip-audit,
writes a CycloneDX JSON SBOM into dist/, and attests the wheel, sdist, and
SBOM.
PyPI and GHCR do not provide one cross-registry transaction, so parallel eligibility is coordinated but not atomic. Treat a tagged run as promoted only after both publication jobs and their post-publication checks succeed. If one registry succeeds and the other fails, stop promotion and inspect the partial state. Retry only the missing job or files from the same release tag and verified inputs; never rerun a successful publication blindly or replace an already published immutable Python version.
Promote a release only from the uploaded workflow artifacts after reviewing the attestations. Roll back a Python release by reinstalling the previous verified wheel or sdist while it remains inside that 90-day artifact window. For older rollback targets, preserve the verified wheel/sdist outside GitHub Actions artifact retention or rebuild from the release tag before promotion. Tagged releases also publish the GHCR image with BuildKit SBOM/provenance and GitHub attestations; container rollbacks should pin the previous verified image digest, not a floating tag.
Documentation ownership¶
README.mdis the compact repository landing page. It keeps the managed-VPS two-command path first and points to the canonical task pages instead of duplicating their complete procedures.docs/quick-start.mdowns the managed VPS first journey, release-image verification, disposable launch, the first file task, and first-run removal.docs/operations.mdowns managed/Docker persistence, stop/remove semantics, destructive cleanup, state locations, resource sizing, and update rollback.SECURITY.mdowns external-exposure policy;docs/public-direct.mdowns the executable public-direct Compose, health, quota, and container-rollback procedure.API.mdand ADR-010 own the protocol/client compatibility contract.CONTRIBUTING.mdand ADR-007 own release publication and Python/container rollback policy.docs/index.md,docs/architecture.md,docs/threat-model.md, anddocs/ADR/*are docs-site pages and staydocs/-canonical.API.md,CHANGELOG.md,CONTRIBUTING.md, andSECURITY.mdare root-canonical and are generated intodocs/bytools/sync_docs.py.- After editing a root-canonical document, run
python tools/sync_docs.py --writeand commit both the root file and its generateddocs/mirror.
Product and package compatibility¶
The current product, distribution, CLI, and public import name is xferry.
New public APIs, examples, and type-checking coverage must use the xferry
namespace.
The deprecated src package remains installed and supported throughout 2.x
for compatibility. Its planned removal boundary is 3.0. Changes in 2.x must
keep the compatibility tests passing, while documentation and new APIs guide
consumers to migrate public imports and module invocations to xferry before
that major release.
Pull request checklist¶
Reviewers look for (the template in .github/PULL_REQUEST_TEMPLATE.md
restates this):
- [ ] Tests added or updated;
pytestgreen locally. - [ ]
ruff check xferry src testspasses. - [ ]
ruff format --check xferry src testspasses. - [ ]
mypy xferry srcpasses with no new errors. - [ ]
CHANGELOG.mdupdated under[Unreleased]with the appropriate section (Added,Changed,Fixed,Security,Performance,Deprecated,Removed). - [ ] Root-canonical docs regenerated with
python tools/sync_docs.py --writeand verified withpython tools/sync_docs.py --check. - [ ] Stale contract and semantic docs guards pass with
python tools/check_stale_docs.py. - [ ] Documentation updated when behavior changes:
README.md,API.md, or a newdocs/ADR/entry. - [ ] Security impact statement in the PR body (say "none" explicitly if the change is unrelated to auth/TLS/crypto/path handling).
Code conventions¶
- Python 3.10-compatible syntax — use
X | None, notOptional[X]. - Type hints on everything that is not a test fixture.
mypy --strictis enforced onxferry/andsrc/. - Paths: always
pathlib.Path; compare withPath.resolve().relative_to()for any user-supplied component (see ADR-004). - Randomness: use
secretsfor anything security-adjacent,randomfor nothing insrc/. - Logger:
logging.getLogger("xferry"). Log what a reader needs to investigate a failure — include the request ID when available. - Error responses: JSON body
{"error": "...", "status": NNN}. - English in user-facing strings (logs, CLI
--help, response bodies). Source-code comments may be in any language the team can read.
Adding a new HTTP method¶
- Create or extend the scoped handler mixin in
src/handlers/. - Define
def handle_<name>(self, request: HTTPRequest) -> HTTPResponse. - Add one
CoreMethodSpecinsrc/features.py; handler registration, CORS, browser-mutation policy, PING discovery, and UI grouping derive from it. - If a new handler mixin is required, add it to
HandlerMixininsrc/handlers/__init__.py. - Document the method in
API.mdand, if it has non-trivial semantics, add a short ADR indocs/ADR/. - Tests:
- Unit tests in
tests/test_handlers/test_<mixin>.py - Integration scenarios in
tests/test_handlers/test_handler_integration.pyif the method interacts with sockets, auth, or advanced upload routing.
Reporting security issues¶
Do not open a public issue. See SECURITY.md for the private
disclosure process.
Questions¶
Open a draft PR or issue — happy to discuss design before code.