walflux 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. walflux-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +60 -0
  2. walflux-0.1.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. walflux-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
  4. walflux-0.1.0/.github/pull_request_template.md +10 -0
  5. walflux-0.1.0/.github/workflows/ci.yml +72 -0
  6. walflux-0.1.0/.github/workflows/release.yml +114 -0
  7. walflux-0.1.0/.gitignore +25 -0
  8. walflux-0.1.0/CHANGELOG.md +34 -0
  9. walflux-0.1.0/CODE_OF_CONDUCT.md +134 -0
  10. walflux-0.1.0/CONTRIBUTING.md +89 -0
  11. walflux-0.1.0/DESIGN.md +298 -0
  12. walflux-0.1.0/Dockerfile +24 -0
  13. walflux-0.1.0/LICENSE +21 -0
  14. walflux-0.1.0/Makefile +35 -0
  15. walflux-0.1.0/PKG-INFO +306 -0
  16. walflux-0.1.0/README.md +276 -0
  17. walflux-0.1.0/SECURITY.md +30 -0
  18. walflux-0.1.0/demo/config.yaml +32 -0
  19. walflux-0.1.0/demo/docker-compose.yml +50 -0
  20. walflux-0.1.0/demo/generate.py +146 -0
  21. walflux-0.1.0/demo/kill9.sh +74 -0
  22. walflux-0.1.0/demo/seed.sql +25 -0
  23. walflux-0.1.0/demo/verify.py +282 -0
  24. walflux-0.1.0/deploy/docker-compose.yml +19 -0
  25. walflux-0.1.0/deploy/walflux.service +26 -0
  26. walflux-0.1.0/docs/OPERATIONS.md +101 -0
  27. walflux-0.1.0/docs/SPEC.md +583 -0
  28. walflux-0.1.0/pyproject.toml +74 -0
  29. walflux-0.1.0/tests/integration/test_concurrency.py +297 -0
  30. walflux-0.1.0/tests/integration/test_end_to_end.py +350 -0
  31. walflux-0.1.0/tests/test_aggregates.py +385 -0
  32. walflux-0.1.0/tests/test_cli.py +81 -0
  33. walflux-0.1.0/tests/test_config.py +347 -0
  34. walflux-0.1.0/tests/test_daemon.py +132 -0
  35. walflux-0.1.0/tests/test_protocol.py +366 -0
  36. walflux-0.1.0/uv.lock +316 -0
  37. walflux-0.1.0/walflux/__init__.py +8 -0
  38. walflux-0.1.0/walflux/__main__.py +10 -0
  39. walflux-0.1.0/walflux/aggregates.py +331 -0
  40. walflux-0.1.0/walflux/bootstrap.py +250 -0
  41. walflux-0.1.0/walflux/checkpoint.py +60 -0
  42. walflux-0.1.0/walflux/cli.py +154 -0
  43. walflux-0.1.0/walflux/common.py +66 -0
  44. walflux-0.1.0/walflux/config.py +247 -0
  45. walflux-0.1.0/walflux/daemon.py +385 -0
  46. walflux-0.1.0/walflux/protocol.py +365 -0
  47. walflux-0.1.0/walflux/py.typed +0 -0
  48. walflux-0.1.0/walflux/replication.py +103 -0
@@ -0,0 +1,60 @@
1
+ name: Bug report
2
+ description: Something WalFlux did wrong — wrong aggregates, a crash, a stuck daemon.
3
+ labels: [bug]
4
+ body:
5
+ - type: input
6
+ id: walflux-version
7
+ attributes:
8
+ label: WalFlux version
9
+ description: Output of `walflux --version` (plus the commit, if installed from git).
10
+ placeholder: walflux 0.1.0
11
+ validations:
12
+ required: true
13
+ - type: input
14
+ id: postgres-version
15
+ attributes:
16
+ label: Postgres version
17
+ description: Output of `SELECT version();`.
18
+ placeholder: PostgreSQL 16.4
19
+ validations:
20
+ required: true
21
+ - type: dropdown
22
+ id: hosting
23
+ attributes:
24
+ label: Where does Postgres run?
25
+ options:
26
+ - Self-hosted
27
+ - Amazon RDS / Aurora
28
+ - Google Cloud SQL
29
+ - Supabase
30
+ - Neon
31
+ - Other managed service
32
+ validations:
33
+ required: true
34
+ - type: textarea
35
+ id: views-config
36
+ attributes:
37
+ label: The config's `views:` section
38
+ description: Redact nothing else — the DSN is not needed.
39
+ render: yaml
40
+ validations:
41
+ required: true
42
+ - type: textarea
43
+ id: logs
44
+ attributes:
45
+ label: Daemon logs around the failure
46
+ description: >
47
+ The daemon logs every flush with its LSN, which usually pinpoints the
48
+ moment things went wrong — include the lines before and after.
49
+ render: text
50
+ validations:
51
+ required: true
52
+ - type: textarea
53
+ id: expected-vs-actual
54
+ attributes:
55
+ label: Expected vs actual
56
+ description: >
57
+ For wrong aggregates, the ideal evidence is the ground-truth `GROUP BY`
58
+ over the source table next to the `walflux.<view>` contents.
59
+ validations:
60
+ required: true
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,27 @@
1
+ name: Feature request
2
+ description: Something WalFlux should do.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: checkboxes
6
+ id: roadmap
7
+ attributes:
8
+ label: Roadmap check
9
+ options:
10
+ - label: >
11
+ I checked the
12
+ [roadmap](https://github.com/sedai77/walflux-postgres-incremental-views#roadmap)
13
+ — joins, min/max, snapshot re-sync, and a Prometheus metrics
14
+ endpoint are already planned.
15
+ required: true
16
+ - type: textarea
17
+ id: problem
18
+ attributes:
19
+ label: The problem
20
+ description: What you are trying to do — not just the feature you want.
21
+ validations:
22
+ required: true
23
+ - type: textarea
24
+ id: proposal
25
+ attributes:
26
+ label: Proposed behavior
27
+ description: Config shape, CLI flags, semantics — whatever you have.
@@ -0,0 +1,10 @@
1
+ ## What & why
2
+
3
+ <!-- Small, focused diffs with a clear "why" land quickly. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `uv run --extra dev pytest -m "not integration"` passes
8
+ - [ ] `uv run --extra dev ruff check .` is clean
9
+ - [ ] Interface changes update [`docs/SPEC.md`](../docs/SPEC.md) — interface changes are spec changes
10
+ - [ ] Behavior changes have tests; anything touching the apply/checkpoint/ack path keeps the `kill -9` integration test honest
@@ -0,0 +1,72 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ name: lint (ruff)
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - name: ruff check
18
+ run: uv run --extra dev ruff check .
19
+
20
+ unit:
21
+ name: unit (py ${{ matrix.python }})
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ python: ["3.10", "3.13"]
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: astral-sh/setup-uv@v5
30
+ with:
31
+ python-version: ${{ matrix.python }}
32
+ - name: pytest (unit)
33
+ run: uv run --extra dev pytest -m "not integration"
34
+
35
+ integration:
36
+ name: integration (postgres 16, wal_level=logical)
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ - uses: astral-sh/setup-uv@v5
41
+ with:
42
+ python-version: "3.12"
43
+ # Service containers cannot override the image command, and we need
44
+ # wal_level=logical — so start Postgres with a plain `docker run`.
45
+ - name: Start Postgres 16 with logical WAL
46
+ run: |
47
+ docker run -d --name pg \
48
+ -e POSTGRES_PASSWORD=postgres \
49
+ -p 5432:5432 \
50
+ postgres:16 \
51
+ -c wal_level=logical \
52
+ -c max_replication_slots=8 \
53
+ -c max_wal_senders=8
54
+ - name: Wait for Postgres
55
+ run: |
56
+ for i in $(seq 1 30); do
57
+ if docker exec pg pg_isready -U postgres -h localhost; then
58
+ exit 0
59
+ fi
60
+ echo "waiting for postgres ($i)..."
61
+ sleep 1
62
+ done
63
+ echo "postgres never became ready" >&2
64
+ docker logs pg >&2
65
+ exit 1
66
+ - name: pytest (integration)
67
+ env:
68
+ WALFLUX_TEST_DSN: postgresql://postgres:postgres@localhost:5432/postgres
69
+ run: uv run --extra dev pytest -m integration -v
70
+ - name: Postgres logs (on failure)
71
+ if: failure()
72
+ run: docker logs pg
@@ -0,0 +1,114 @@
1
+ # Release pipeline, triggered by pushing a v* tag (e.g. v0.1.0).
2
+ #
3
+ # No API tokens anywhere: PyPI uses Trusted Publishing (OIDC) and ghcr.io uses
4
+ # the built-in GITHUB_TOKEN. One-time setup by the repo owner:
5
+ # 1. pypi.org -> account -> Publishing -> add a (pending) trusted publisher:
6
+ # project "walflux", owner "sedai77",
7
+ # repository "walflux-postgres-incremental-views",
8
+ # workflow "release.yml", environment "pypi".
9
+ # 2. GitHub repo Settings -> Environments -> create "pypi".
10
+ # Then: bump __version__ in walflux/__init__.py, move the CHANGELOG.md
11
+ # Unreleased entries into a dated section, and push the matching tag.
12
+ name: Release
13
+
14
+ on:
15
+ push:
16
+ tags: ["v*"]
17
+
18
+ jobs:
19
+ build:
20
+ name: build (wheel + sdist)
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: astral-sh/setup-uv@v5
25
+ with:
26
+ python-version: "3.12"
27
+ - name: Build
28
+ run: uv build
29
+ - name: Check metadata
30
+ run: uvx twine check dist/*
31
+ - name: Tag must match the package version
32
+ run: |
33
+ version="${GITHUB_REF_NAME#v}"
34
+ if [ ! -f "dist/walflux-${version}.tar.gz" ]; then
35
+ echo "tag ${GITHUB_REF_NAME} does not match the built version:" >&2
36
+ ls dist/ >&2
37
+ echo "bump __version__ in walflux/__init__.py before tagging" >&2
38
+ exit 1
39
+ fi
40
+ - uses: actions/upload-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ pypi:
46
+ name: publish to PyPI (trusted publishing)
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ environment: pypi
50
+ permissions:
51
+ id-token: write # OIDC is the whole credential — no token, no secret
52
+ steps:
53
+ - uses: actions/download-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+ - uses: pypa/gh-action-pypi-publish@release/v1
58
+
59
+ docker:
60
+ name: publish image (ghcr.io)
61
+ needs: build
62
+ runs-on: ubuntu-latest
63
+ permissions:
64
+ contents: read
65
+ packages: write
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: docker/setup-qemu-action@v3
69
+ - uses: docker/setup-buildx-action@v3
70
+ - uses: docker/login-action@v3
71
+ with:
72
+ registry: ghcr.io
73
+ username: ${{ github.actor }}
74
+ password: ${{ secrets.GITHUB_TOKEN }}
75
+ - uses: docker/build-push-action@v6
76
+ with:
77
+ context: .
78
+ target: runtime
79
+ platforms: linux/amd64,linux/arm64
80
+ push: true
81
+ tags: |
82
+ ghcr.io/${{ github.repository }}:${{ github.ref_name }}
83
+ ghcr.io/${{ github.repository }}:latest
84
+
85
+ github-release:
86
+ name: GitHub release
87
+ needs: [build, pypi]
88
+ runs-on: ubuntu-latest
89
+ permissions:
90
+ contents: write
91
+ steps:
92
+ - uses: actions/checkout@v4
93
+ - uses: actions/download-artifact@v4
94
+ with:
95
+ name: dist
96
+ path: dist/
97
+ - name: Extract this version's notes from CHANGELOG.md
98
+ run: |
99
+ awk -v heading="## [${GITHUB_REF_NAME#v}]" '
100
+ index($0, heading) == 1 { found = 1; next }
101
+ found && (/^## / || /^\[.*\]: /) { exit }
102
+ found { print }
103
+ ' CHANGELOG.md > notes.md
104
+ if [ ! -s notes.md ]; then
105
+ echo "no CHANGELOG.md section for ${GITHUB_REF_NAME#v}" >&2
106
+ echo "See CHANGELOG.md." > notes.md
107
+ fi
108
+ - name: Create the release
109
+ env:
110
+ GH_TOKEN: ${{ github.token }}
111
+ run: |
112
+ gh release create "$GITHUB_REF_NAME" dist/* \
113
+ --title "WalFlux ${GITHUB_REF_NAME#v}" \
114
+ --notes-file notes.md
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+
14
+ # Environment
15
+ .env
16
+ *.local.yaml
17
+
18
+ # Demo artifacts
19
+ demo/.pgdata/
20
+ *.log
21
+
22
+ # OS / editor
23
+ .DS_Store
24
+ .idea/
25
+ .vscode/
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to WalFlux are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
+ [semver](https://semver.org/) with the usual 0.x caveat: breaking changes may
6
+ land in minor versions (0.2.0), fixes in patch versions (0.1.1). Anything
7
+ behavior-affecting — and anything that requires `walflux setup --force` — is
8
+ called out explicitly in its entry.
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.1.0] - 2026-07-26
13
+
14
+ Initial release.
15
+
16
+ ### Added
17
+
18
+ - `pgoutput` (protocol v1) decoder for logical replication messages.
19
+ - Incrementally maintained `count` / `sum` / `avg` aggregate tables with
20
+ `GROUP BY` — including NULL group keys and global (no `group_by`) views.
21
+ - Exactly-once apply: each batch's deltas and its replication checkpoint commit
22
+ in one target transaction; redelivered transactions are discarded by commit
23
+ LSN. Proven across `kill -9` by the integration suite and `make kill9`.
24
+ - Consistent snapshot backfill: `walflux setup` builds targets from the slot's
25
+ exported snapshot, so backfill and stream meet with no gap and no overlap.
26
+ - CLI: `walflux setup` / `run` / `status` / `teardown`, plus
27
+ `python -m walflux` as an alternative entry point.
28
+ - A PEP 561 `py.typed` marker: the package's type annotations are visible to
29
+ mypy/pyright in downstream projects.
30
+ - A wrong or unreachable DSN fails as one tidy `cannot connect to database`
31
+ error line instead of a raw psycopg2 traceback.
32
+
33
+ [Unreleased]: https://github.com/sedai77/walflux-postgres-incremental-views/compare/v0.1.0...HEAD
34
+ [0.1.0]: https://github.com/sedai77/walflux-postgres-incremental-views/releases/tag/v0.1.0
@@ -0,0 +1,134 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances
31
+ of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official e-mail address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement by contacting the
63
+ repository maintainers through GitHub — a direct message, or
64
+ [GitHub's report abuse form](https://github.com/contact/report-abuse) for
65
+ serious cases. All complaints will be reviewed and investigated promptly and
66
+ fairly.
67
+
68
+ All community leaders are obligated to respect the privacy and security of the
69
+ reporter of any incident.
70
+
71
+ ## Enforcement Guidelines
72
+
73
+ Community leaders will follow these Community Impact Guidelines in determining
74
+ the consequences for any action they deem in violation of this Code of Conduct:
75
+
76
+ ### 1. Correction
77
+
78
+ **Community Impact**: Use of inappropriate language or other behavior deemed
79
+ unprofessional or unwelcome in the community.
80
+
81
+ **Consequence**: A private, written warning from community leaders, providing
82
+ clarity around the nature of the violation and an explanation of why the
83
+ behavior was inappropriate. A public apology may be requested.
84
+
85
+ ### 2. Warning
86
+
87
+ **Community Impact**: A violation through a single incident or series of
88
+ actions.
89
+
90
+ **Consequence**: A warning with consequences for continued behavior. No
91
+ interaction with the people involved, including unsolicited interaction with
92
+ those enforcing the Code of Conduct, for a specified period of time. This
93
+ includes avoiding interactions in community spaces as well as external channels
94
+ like social media. Violating these terms may lead to a temporary or permanent
95
+ ban.
96
+
97
+ ### 3. Temporary Ban
98
+
99
+ **Community Impact**: A serious violation of community standards, including
100
+ sustained inappropriate behavior.
101
+
102
+ **Consequence**: A temporary ban from any sort of interaction or public
103
+ communication with the community for a specified period of time. No public or
104
+ private interaction with the people involved, including unsolicited interaction
105
+ with those enforcing the Code of Conduct, is allowed during this period.
106
+ Violating these terms may lead to a permanent ban.
107
+
108
+ ### 4. Permanent Ban
109
+
110
+ **Community Impact**: Demonstrating a pattern of violation of community
111
+ standards, including sustained inappropriate behavior, harassment of an
112
+ individual, or aggression toward or disparagement of classes of individuals.
113
+
114
+ **Consequence**: A permanent ban from any sort of public interaction within the
115
+ community.
116
+
117
+ ## Attribution
118
+
119
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
120
+ version 2.1, available at
121
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
122
+
123
+ Community Impact Guidelines were inspired by
124
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
128
+ [https://www.contributor-covenant.org/translations][translations].
129
+
130
+ [homepage]: https://www.contributor-covenant.org
131
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
132
+ [Mozilla CoC]: https://github.com/mozilla/diversity
133
+ [FAQ]: https://www.contributor-covenant.org/faq
134
+ [translations]: https://www.contributor-covenant.org/translations
@@ -0,0 +1,89 @@
1
+ # Contributing to WalFlux
2
+
3
+ Thanks for helping out. This is a small, sharply-scoped codebase — the fastest way to
4
+ contribute well is to read [`docs/SPEC.md`](docs/SPEC.md) first. It is the authoritative
5
+ contract for every module's interface, and pull requests are reviewed against it.
6
+ The reasoning behind the design lives in [`DESIGN.md`](DESIGN.md).
7
+
8
+ ## Development setup
9
+
10
+ WalFlux uses [uv](https://docs.astral.sh/uv/) for everything. There is nothing to
11
+ "install" beyond cloning:
12
+
13
+ ```bash
14
+ git clone https://github.com/sedai77/walflux-postgres-incremental-views.git
15
+ cd walflux-postgres-incremental-views
16
+ uv run --python 3.12 --extra dev pytest -m "not integration"
17
+ ```
18
+
19
+ `uv run` resolves and caches the environment (runtime deps: `psycopg2-binary`,
20
+ `PyYAML`; dev deps: `pytest`, `ruff`) on first use.
21
+
22
+ ## Running the tests
23
+
24
+ Unit tests need no database:
25
+
26
+ ```bash
27
+ uv run --python 3.12 --extra dev pytest -m "not integration"
28
+ ```
29
+
30
+ The integration suite needs a Postgres 15+ with `wal_level=logical` and a
31
+ superuser-ish DSN in `WALFLUX_TEST_DSN` (it creates replication slots, publications,
32
+ and scratch schemas). The easiest way is docker:
33
+
34
+ ```bash
35
+ docker run -d --name walflux-test-pg \
36
+ -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:16 \
37
+ -c wal_level=logical -c max_replication_slots=8 -c max_wal_senders=8
38
+
39
+ WALFLUX_TEST_DSN=postgresql://postgres:postgres@localhost:5432/postgres \
40
+ uv run --python 3.12 --extra dev pytest -m integration -v
41
+
42
+ docker rm -f walflux-test-pg # when done
43
+ ```
44
+
45
+ Without `WALFLUX_TEST_DSN` the integration tests skip with an explanatory reason —
46
+ that is expected locally.
47
+
48
+ CI runs three jobs on every push and pull request: `ruff check`, the unit suite on
49
+ Python 3.10 and 3.13 (the oldest and newest supported), and the integration suite
50
+ against Postgres 16. All three must pass.
51
+
52
+ ## Style
53
+
54
+ - `ruff check .` must be clean (`uv run --python 3.12 --extra dev ruff check .`);
55
+ configuration lives in `pyproject.toml`.
56
+ - Full type hints, `from __future__ import annotations` at the top of every module.
57
+ - Docstrings on public functions. Comments are reserved for invariants the code
58
+ cannot express (the skip rule, the snapshot handshake, feedback ordering).
59
+ - SQL identifiers go through `walflux.common.quote_ident()`; values are always
60
+ parameterized (`%s`), never interpolated.
61
+ - No new runtime dependencies. `psycopg2-binary` + `PyYAML` is the complete list,
62
+ and keeping it that way is a feature. psycopg2 imports stay inside the modules
63
+ that talk to the database (`replication.py`, `bootstrap.py`, `checkpoint.py`,
64
+ `daemon.py`) so everything else is importable without a database.
65
+
66
+ ## Making changes
67
+
68
+ 1. Check whether your change conflicts with [`docs/SPEC.md`](docs/SPEC.md). If it
69
+ does, the PR should update the spec too — interface changes are spec changes.
70
+ 2. Add or update tests. Behavior changes to the protocol decoder, delta arithmetic,
71
+ or config validation need unit tests; anything touching the apply/checkpoint/ack
72
+ path needs to keep the kill -9 integration test honest.
73
+ 3. Keep PRs focused. Small, reviewable diffs with a clear "why" in the description
74
+ land quickly.
75
+
76
+ Bug reports are most useful with: Postgres version, WalFlux version, the config's
77
+ `views:` section, and daemon logs around the failure (the daemon logs every flush
78
+ with its LSN, which usually pinpoints the moment things went wrong).
79
+
80
+ ## Repository metadata (for maintainers)
81
+
82
+ Suggested GitHub "About" description:
83
+
84
+ > Millisecond-fresh materialized views for Postgres via logical replication —
85
+ > no extensions, no triggers, no second database.
86
+
87
+ Suggested topics: `postgres`, `postgresql`, `materialized-views`,
88
+ `incremental-view-maintenance`, `change-data-capture`, `logical-replication`,
89
+ `pgoutput`, `cdc`, `streaming`, `python`.