partition_gardener 0.3.6 → 0.3.7

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +1 -1
  4. data/docs/THIRD_PARTY_LICENSE_MANIFEST.tsv +19 -19
  5. data/docs/changelogs/20260709120900_partition-engines-guide.md +1 -1
  6. data/docs/changelogs/20260813142300_rebalance-before-archive-attach.md +1 -1
  7. data/docs/changelogs/20260813144516_relock-appraisal-advisory-json.md +1 -1
  8. data/docs/changelogs/20260902114500_align-child-columns-before-attach.md +1 -1
  9. data/docs/changelogs/20260902124500_cut-patch-release-0-3-4.md +1 -1
  10. data/docs/changelogs/20260909143500_occupied-window-current-origin.md +1 -1
  11. data/docs/changelogs/20260909154900_tail-slot-rfc.md +1 -1
  12. data/docs/changelogs/20260909162554_high-end-future-occupancy.md +1 -1
  13. data/docs/changelogs/20260909164000_shipped-contract-rfcs.md +1 -1
  14. data/docs/changelogs/20260909165500_cut-patch-release-0-3-5.md +1 -1
  15. data/docs/changelogs/20260916144600_public-identifiers-contract.md +1 -1
  16. data/docs/changelogs/20261005135700_checkpoint-table-name.md +1 -1
  17. data/docs/changelogs/20261005141621_cut-patch-release-0-3-6.md +1 -1
  18. data/docs/changelogs/20261021220000_cut-patch-release-0-3-7.md +24 -0
  19. data/docs/issues/20260909143500_occupied-window-current-origin.md +1 -1
  20. data/docs/issues/20260909164000_shipped-contract-rfcs.md +1 -1
  21. data/docs/issues/20260916113300_opaque-composite-public-ids.md +1 -1
  22. data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +1 -1
  23. data/docs/reviews/20261021220000_engineering-audit-0-3-7.md +30 -0
  24. data/docs/reviews/20261028075216_engineering-audit-release-preparation.md +35 -0
  25. data/docs/reviews/20261029150855_engineering-audit-release-preparation.md +43 -0
  26. data/docs/schemas/partition_garden.schema.json +16 -0
  27. data/lib/partition_gardener/archive_retention.rb +7 -3
  28. data/lib/partition_gardener/config_document.rb +4 -0
  29. data/lib/partition_gardener/date_calendar.rb +2 -5
  30. data/lib/partition_gardener/gap_detection.rb +1 -1
  31. data/lib/partition_gardener/migration/hot_switch_concern.rb +24 -15
  32. data/lib/partition_gardener/plan_applier.rb +17 -3
  33. data/lib/partition_gardener/version.rb +1 -1
  34. data/sig/partition_gardener.rbs +2 -2
  35. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 168cd3dce806746b67801bac88f82c1bff76d9617aa7d11bb543a21476f92810
4
- data.tar.gz: 3b89596e3ee530b5ea262b3d9e7e6396077b6198ad811951867e113e959c335a
3
+ metadata.gz: e5f9084c2b05bdad581f87e5b137cc7321106f2a4a39e3d52def523acd74f0b7
4
+ data.tar.gz: 86ba83786f0cf06729e041e758e8034253e549677237160bb3dc0c09716ec071
5
5
  SHA512:
6
- metadata.gz: f55dcaca8b968cb74fbd77af80a7f8c14dbc7419274fdbbbab2fd0cfb11a2098e0a36b70908722180cbe344b87b18b532b68951e20be054cb1738c011566c009
7
- data.tar.gz: f416553eda6be0049cc5dfdcd0fcbd4ef929c85b3e6411dc1fce3c210b87927f8ea846a6220705b7ca3ffc892b5539548eb5f342e8dd5b0f83324b68c6f5de7b
6
+ metadata.gz: c3c13183c16a47fc4b479a7c4311e689a2081e9dddc0c60f8a590c5e4cdffa4e56db1f8e270e7cf4119772398f1f286baaa4ab985b20ef743b8fa2854a38b47e
7
+ data.tar.gz: 702c2a0f9d29d5c0a63894172ced8ab62cf47f70ab84f0fbcd02a9e648455cfed78927d187ca41538612140c7271b51363937fa60a1be501a24e4c3bb3ac42a5
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.7 (2026-10-29)
6
+
7
+ - Fix rebalance resume so phase `detach` still runs detach and drain until the record advances to `segments`
8
+ - Refuse `CREATE TABLE IF NOT EXISTS … PARTITION OF` when a detached table already owns the planned child name
9
+ - Drop detached remove candidates only when they hold zero rows
10
+ - Rename hot-switch children when the live table name is a suffix of the shadow prefix (for example `p_events` → `events`)
11
+ - Repoint serial sequences after hot-switch by setting `nextval` default and `setval` on the live table
12
+ - Fail closed in `sync_delta_data` when `sync_stale_column` is missing from source or target
13
+ - Sort gap detection bounds with native `Comparable` order so integer windows are not lexicographic
14
+ - Advance `DateCalendar.add_years` through leap days via `Date#>>`
15
+ - Notify detach versus drop for archive retention when `retention_keep_table` is set
16
+ - Export and accept `active_id_lo`, `active_id_width`, `current_band_size`, and `archive_band_size` in JSON registry documents
17
+ - Align README Ruby requirement and RBS `statement_timeout` types with the gemspec
18
+
5
19
  ## 0.3.6 (2026-10-05)
6
20
 
7
21
  - Store rebalance checkpoints in `partition_gardener_checkpoints` by default (previously `partition_gardener_run_records`)
data/README.md CHANGED
@@ -6,7 +6,7 @@ PostgreSQL partition lifecycle: archive, current, and future zones, heat-driven
6
6
 
7
7
  ## Requirements
8
8
 
9
- - Ruby >= 3.2
9
+ - Ruby >= 3.4
10
10
  - PostgreSQL with declarative partitioning
11
11
  - `pg` gem (runtime dependency)
12
12
  - Rails >= 7.1 optional — railtie loads when Rails is present and sets Active Record connection plus `Time.zone.today`
@@ -1,60 +1,60 @@
1
1
  bundle gem version licenses
2
- root activemodel 8.1.3.1 MIT
3
- root activerecord 8.1.3.1 MIT
4
- root activesupport 8.1.3.1 MIT
2
+ root activemodel 8.1.4 MIT
3
+ root activerecord 8.1.4 MIT
4
+ root activesupport 8.1.4 MIT
5
5
  root appraisal 2.5.0 MIT
6
6
  root ast 2.4.3 MIT
7
7
  root base64 0.3.0 Ruby|BSD-2-Clause
8
- root bigdecimal 4.1.2 Ruby|BSD-2-Clause
8
+ root bigdecimal 4.1.3 Ruby|BSD-2-Clause
9
9
  root bundler 4.0.16 MIT
10
10
  root concurrent-ruby 1.3.8 MIT
11
11
  root connection_pool 3.0.2 MIT
12
12
  root diff-lcs 1.6.2 MIT|Artistic-1.0-Perl|GPL-2.0-or-later
13
13
  root drb 2.2.3 Ruby|BSD-2-Clause
14
14
  root i18n 1.15.2 MIT
15
- root json 2.21.2 Ruby
15
+ root json 3.1.0 Ruby
16
16
  root language_server-protocol 3.17.0.6 MIT
17
17
  root lint_roller 1.1.0 MIT
18
18
  root logger 1.7.0 Ruby|BSD-2-Clause
19
19
  root minitest 6.0.6 MIT
20
- root parallel 2.1.0 MIT
21
- root parser 3.3.12.0 MIT
22
- root partition_gardener 0.3.6 MIT
20
+ root parallel 2.3.0 MIT
21
+ root parser 3.3.13.0 MIT
22
+ root partition_gardener 0.3.7 MIT
23
23
  root pg 1.6.3 BSD-2-Clause
24
- root polyrun 2.2.3 MIT
24
+ root polyrun 2.2.5 MIT
25
25
  root prism 1.9.0 MIT
26
26
  root prosopite 2.2.0 Apache-2.0
27
27
  root racc 1.8.1 Ruby|BSD-2-Clause
28
- root rack 3.2.6 MIT
28
+ root rack 3.2.7 MIT
29
29
  root rainbow 3.1.1 MIT
30
30
  root rake 13.4.2 MIT
31
31
  root rbs 4.2.0 BSD-2-Clause|Ruby
32
- root regexp_parser 2.12.0 MIT
32
+ root regexp_parser 2.13.1 MIT
33
33
  root rspec 3.13.2 MIT
34
34
  root rspec-core 3.13.6 MIT
35
35
  root rspec-expectations 3.13.5 MIT
36
36
  root rspec-mocks 3.13.8 MIT
37
37
  root rspec-support 3.13.7 MIT
38
- root rubocop 1.88.2 MIT
38
+ root rubocop 1.91.0 MIT
39
39
  root rubocop-ast 1.50.0 MIT
40
40
  root rubocop-capybara 2.23.0 MIT
41
41
  root rubocop-factory_bot 2.28.0 MIT
42
42
  root rubocop-performance 1.26.1 MIT
43
43
  root rubocop-rails 2.34.3 MIT
44
44
  root rubocop-rspec 3.10.2 MIT
45
- root rubocop-rspec_rails 2.32.0 MIT
46
- root rubocop-thread_safety 0.7.3 MIT
45
+ root rubocop-rspec_rails 2.33.0 MIT
46
+ root rubocop-thread_safety 0.8.0 MIT
47
47
  root ruby-progressbar 1.13.0 MIT
48
48
  root securerandom 0.4.1 Ruby|BSD-2-Clause
49
- root standard 1.56.0
49
+ root standard 1.57.0
50
50
  root standard-custom 1.0.2 MIT
51
51
  root standard-performance 1.9.0 MIT
52
52
  root standard-rails 1.6.0 MIT
53
- root standard-rspec 0.5.0 MIT
53
+ root standard-rspec 0.5.1 MIT
54
54
  root thor 1.5.0 MIT
55
55
  root timeout 0.6.1 Ruby|BSD-2-Clause
56
56
  root tsort 0.2.0 Ruby|BSD-2-Clause
57
57
  root tzinfo 2.0.6 MIT
58
- root unicode-display_width 3.2.0 MIT
59
- root unicode-emoji 4.2.0 MIT
60
- root uri 1.1.1 Ruby|BSD-2-Clause
58
+ root unicode-display_width 3.3.0 MIT
59
+ root unicode-emoji 4.3.0 MIT
60
+ root uri 1.1.2 Ruby|BSD-2-Clause
@@ -1,6 +1,6 @@
1
1
  ## Participants
2
2
 
3
- Andrei Makarov, agent-assisted release prep.
3
+ Andrei Makarov (@amkisko)
4
4
 
5
5
  ## Decisions
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -0,0 +1,24 @@
1
+ # Cut patch release 0.3.7
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Bump `PartitionGardener::VERSION` to `0.3.7`.
10
+ - Record user-visible correctness and contract fixes in `CHANGELOG.md` under `## 0.3.7 (2026-10-29)`.
11
+
12
+ ## Effects
13
+
14
+ - Resume, hot-switch, gap detection, calendar leap day, retention notify, and integer-window JSON registry fixes landed on `patch/engineering-audit-0-3-7`.
15
+ - Release script now uses `git status --porcelain` and loads `PartitionGardener::VERSION` directly.
16
+
17
+ ## Next
18
+
19
+ - Commit the prepared tree on `main`, tag `0.3.7`, push, and run `make release` (gem push, tag push, GitHub release). See `docs/reviews/20261029150855_engineering-audit-release-preparation.md`.
20
+
21
+ ## Source
22
+
23
+ - Review: `docs/reviews/20261021220000_engineering-audit-0-3-7.md`
24
+ - `CHANGELOG.md`, `lib/partition_gardener/version.rb`
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Participants
4
4
 
5
- Andrei
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -2,7 +2,7 @@ CI quality failed on a trailing blank line in spec/support/junit_formatter.rb. T
2
2
 
3
3
  ## Participants
4
4
 
5
- - amkisko
5
+ Andrei Makarov (@amkisko)
6
6
 
7
7
  ## Decisions
8
8
 
@@ -0,0 +1,30 @@
1
+ # Engineering audit and 0.3.7 fixes
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Run a multi-model engineering audit on `partition_gardener` (correctness, tests, hot-path, contracts).
10
+ - Fact-check findings in source before shipping.
11
+ - Ship high-confidence correctness and contract fixes as patch `0.3.7`.
12
+ - Defer structural refactors (PlanApplier phase machine collapse, HotSwitchConcern split, strategy mixin extraction, default statement_timeout wrapper) to later work.
13
+
14
+ ## Effects
15
+
16
+ - Confirmed and fixed: resume skips detach while phase is `detach`; hot-switch child rename skip on `p_` prefix; serial sequence default left on `_old`; unbounded `sync_delta_data` when stale column missing; `CREATE IF NOT EXISTS … PARTITION OF` no-op on detached name; lexicographic integer gap sort; leap-day `add_years`; retention notify says drop when table kept; JSON registry omits integer window keys.
17
+ - Unit specs: 151 examples, 0 failures (`bundle exec rspec` on non-integration paths from `spec/spec_paths.txt`).
18
+ - Integration resume examples extended for row-count preservation and started-but-not-detached phase; not run in this session (needs `INTEGRATION=1` and Postgres).
19
+
20
+ ## Next
21
+
22
+ - Run `INTEGRATION=1` resume and hot-switch specs before cutting the gem.
23
+ - Run `ruby usr/bin/release.rb` when the working tree is clean and ready to push to RubyGems.
24
+ - Follow-up: session advisory lock default for long copies; statement_timeout wrapper; RFC 0002 status; dead `SCHEMA_PATH` wiring or removal.
25
+
26
+ ## Source
27
+
28
+ - Audit agents: correctness, tests/coverage, hot-path/resource, org/contracts.
29
+ - Branch: `patch/engineering-audit-0-3-7`
30
+ - Related changelog: `docs/changelogs/20261021220000_cut-patch-release-0-3-7.md`
@@ -0,0 +1,35 @@
1
+ # Engineering audit and release preparation
2
+
3
+ ## Decisions
4
+
5
+ - Continue on main while preserving the pre-existing uncommitted changes. Do not commit, tag, push, or publish.
6
+ - Retain release version 0.3.7. Reconcile root and appraisal PATH metadata without updating third-party dependency versions.
7
+ - Review correctness, executable coverage, workflow triggers and permissions, package boundaries, resource cost, and trace/identity boundaries. Re-audit the changed paths after corrections.
8
+
9
+ ## Effects
10
+
11
+ - Correct the resume integration fixture's initial expectation: the source rows initially belong to the attached current partition, and the hot monthly partition exists after rebalance.
12
+ - Restore the required lint gate by combining adjacent boundary stubs. Verify all integration examples against an isolated PostgreSQL server, including the existing rebalance and hot-switch changes.
13
+ - Synchronize the own-gem PATH version in the root and appraisal lockfiles to 0.3.7, enable pull-request tests, and restrict workflow permissions to contents: read.
14
+ - Fix the release confirmation guard in usr/bin/release.rb so EOF refuses publication. An explicit Enter still selects the displayed default. The process-boundary regression test executes the release script with command execution replaced at the operating-system boundary; no publication command runs against a real service. The shared regression was observed first in Favorites: 2 examples, 1 failure before correction and 2 examples, 0 failures after correction. The release specs pass in every repository.
15
+ - Final serial JSON report: 188 examples, 0 failures with INTEGRATION=1 and PostgreSQL; no pending integration examples.
16
+ - Required project checks passed: DATABASE_URL=postgres://postgres@127.0.0.1:55432/partition_gardener_test make test. Environment: Ruby 4.0.2, BUNDLE_PATH=vendor/bundle; the installed native extensions require that Ruby build.
17
+ - Build the gem, verify it with Gem::Package#verify, and check that the changelog heading matches the gem version. Final package measurements are recorded in the session output; this gem includes the review note itself.
18
+ - Cold process measurement on macOS arm64 with /usr/bin/time -l and bundle exec ruby -Ilib: 0.35 CPU seconds, 0.7 elapsed seconds, 31817728 peak RSS bytes. Favorites loads Rails before the engine. Local smoke ceilings: 1 MiB compressed gem, 128 MiB RSS, 2 CPU seconds. All measured values fit those ceilings. CPU seconds are the energy proxy; electricity use was not measured.
19
+ - Parse every workflow YAML, verify lockfile own-gem versions, and run git diff --check. All passed. All six assigned repositories remain on main.
20
+
21
+ ## Next
22
+
23
+ - No confirmed correction remains in the reviewed changes. Hosted GitHub Actions and the complete supported Ruby/framework matrix were not executed locally.
24
+ - Scope limits: production traffic, production database size and query plans, external service retention, and co-located resource interference were not measured. The local byte/RSS/CPU results do not establish those properties.
25
+ - Trace review follows local file/log and protocol emissions. No live customer identities or credentials were used. Data retention and deletion in embedding applications remain host contracts.
26
+ - Learned-system training/evaluation and cloud deployment modes do not apply to these library trees. Native memory-boundary mode applies to Polyrun's extension; browser presentation applies to Favorites; HTTP/identity and message companions apply only to repositories that expose those boundaries. Libraries have no independent production health endpoint to assess.
27
+
28
+ ## Source
29
+
30
+ - AGENTS.md
31
+ - .agents/skills/engineering-audit/SKILL.md
32
+ - .agents/skills/changelog-update/SKILL.md
33
+ - CHANGELOG.md
34
+ - Makefile
35
+ - spec/README.md
@@ -0,0 +1,43 @@
1
+ # Engineering audit and release preparation
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Continue on main while preserving uncommitted work. Do not commit, tag, push, or publish.
10
+ - Keep release version 0.3.7. Set the CHANGELOG heading date to 2026-10-29. RubyGems still lists 0.3.6 as the latest published version.
11
+ - Re-check correctness, executable coverage, workflow triggers and permissions, package boundaries, resource cost, and database-boundary behavior after the prior release-prep pass.
12
+
13
+ ## Effects
14
+
15
+ - Enable `pull_request` on the test workflow, keep token permissions at `contents: read`, and cancel superseded runs.
16
+ - Set the `CHANGELOG.md` heading for 0.3.7 to 2026-10-29 and align the cut note under `docs/changelogs/`.
17
+ - Final serial JSON report: 188 examples, 0 failures with `INTEGRATION=1` and PostgreSQL on port 5432; no pending examples. Parallel polyrun with five workers: exit 0.
18
+ - Required project checks passed: rubocop (116 files, no offenses), rbs validate, `INTEGRATION=1` polyrun parallel-rspec. Environment: Ruby 4.0.2, `BUNDLE_PATH=vendor/bundle`.
19
+ - `release_spec`: 2 examples, 0 failures (EOF refuses gem push; Enter accepts the displayed default with system execution replaced).
20
+ - Build the gem, verify it with `Gem::Package#verify`, and confirm the changelog heading matches version 0.3.7. Compressed package: 117760 bytes; source payload: 495104 bytes across 104 packaged paths. These are compressed versus uncompressed sizes, not interchangeable quantities.
21
+ - Cold process measurement on macOS arm64 with `/usr/bin/time -p` and `bundle exec ruby -Ilib`: 0.17 CPU seconds, 0.26 elapsed seconds. Peak RSS via `/usr/bin/time -l` was unavailable in this session (`sysctl kern.clockrate` refused). Prior release-prep peak RSS on the same require path was 31817728 bytes. Local smoke ceilings: 1 MiB compressed gem, 128 MiB RSS, 2 CPU seconds. Measured gem size and CPU fit those ceilings. CPU seconds are the energy proxy; electricity use was not measured.
22
+ - Parse every workflow YAML, verify lockfile own-gem versions at 0.3.7, and run `git diff --check`. All passed.
23
+
24
+ ## Next
25
+
26
+ - Commit, tag 0.3.7, push, and publish remain operator steps; this session did not run them. When the tree is clean, `make release` runs gem push, tag, and GitHub release.
27
+ - Hosted GitHub Actions and the complete supported Ruby/framework matrix were not executed locally beyond the default Gemfile path used by `make test`.
28
+ - Re-measure peak RSS with `/usr/bin/time -l` outside the restricted session if a fresh number is required before publish.
29
+ - Scope limits: production traffic, production database size and query plans, external service retention, and co-located resource interference were not measured. The local byte/CPU results do not establish those properties.
30
+ - Trace review follows local file/log and protocol emissions. No live customer identities or credentials were used. Data retention and deletion in embedding applications remain host contracts.
31
+ - Learned-system training/evaluation and cloud deployment modes do not apply to this library tree. Database-boundary mode applies to PostgreSQL maintenance DDL and checkpoint resume. The library has no independent production health endpoint to assess.
32
+
33
+ ## Source
34
+
35
+ - AGENTS.md
36
+ - .agents/skills/engineering-audit/SKILL.md
37
+ - .agents/skills/changelog-update/SKILL.md
38
+ - CHANGELOG.md
39
+ - Makefile
40
+ - spec/README.md
41
+ - docs/reviews/20261028075216_engineering-audit-release-preparation.md
42
+ - docs/reviews/20261021220000_engineering-audit-0-3-7.md
43
+ - docs/changelogs/20261021220000_cut-patch-release-0-3-7.md
@@ -60,6 +60,22 @@
60
60
  "minimum": 1,
61
61
  "maximum": 10
62
62
  },
63
+ "active_id_lo": {
64
+ "type": "integer",
65
+ "minimum": 0
66
+ },
67
+ "active_id_width": {
68
+ "type": "integer",
69
+ "minimum": 1
70
+ },
71
+ "current_band_size": {
72
+ "type": "integer",
73
+ "minimum": 1
74
+ },
75
+ "archive_band_size": {
76
+ "type": "integer",
77
+ "minimum": 1
78
+ },
63
79
  "premake_months": {
64
80
  "type": "integer",
65
81
  "minimum": 0,
@@ -35,15 +35,18 @@ module PartitionGardener
35
35
  next if bucket >= DateCalendar.beginning_of_month(cutoff)
36
36
 
37
37
  if apply_retention
38
+ keep_table = @config.fetch(:retention_keep_table, false)
38
39
  @executor.detach_partition(table_name, partition.name, concurrently: detach_concurrently?)
39
- @executor.drop_table(partition.name) unless @config.fetch(:retention_keep_table, false)
40
+ @executor.drop_table(partition.name) unless keep_table
40
41
 
42
+ action = keep_table ? "Detached" : "Dropped"
41
43
  PartitionGardener.configuration.notify(
42
- "[PartitionGardener] Dropped archive partition #{partition.name} (retention #{retention_months} months)",
44
+ "[PartitionGardener] #{action} archive partition #{partition.name} (retention #{retention_months} months)",
43
45
  context: {
44
46
  table_name: table_name,
45
47
  partition_name: partition.name,
46
48
  retention_months: retention_months,
49
+ retention_keep_table: keep_table,
47
50
  job: @job_class_name
48
51
  }
49
52
  )
@@ -51,8 +54,9 @@ module PartitionGardener
51
54
  next
52
55
  end
53
56
 
57
+ preview_action = @config.fetch(:retention_keep_table, false) ? "detach" : "drop"
54
58
  PartitionGardener.configuration.notify(
55
- "[PartitionGardener] Would drop archive partition #{partition.name} (bucket #{bucket}, retention #{retention_months} months)",
59
+ "[PartitionGardener] Would #{preview_action} archive partition #{partition.name} (bucket #{bucket}, retention #{retention_months} months)",
56
60
  context: {
57
61
  table_name: table_name,
58
62
  partition_name: partition.name,
@@ -14,6 +14,10 @@ module PartitionGardener
14
14
  active_weeks
15
15
  active_quarters
16
16
  active_years
17
+ active_id_lo
18
+ active_id_width
19
+ current_band_size
20
+ archive_band_size
17
21
  premake_months
18
22
  split_row_threshold
19
23
  move_batch_size
@@ -52,14 +52,11 @@ module PartitionGardener
52
52
  end
53
53
 
54
54
  def add_years(date, count)
55
- value = to_date(date)
56
- Date.new(value.year + count, value.month, value.day)
55
+ to_date(date) >> (count * 12)
57
56
  end
58
57
 
59
58
  def add_months(date, count)
60
- base = beginning_of_month(date)
61
- month_index = (base.year * 12) + (base.month - 1) + count
62
- Date.new(month_index / 12, (month_index % 12) + 1, 1)
59
+ beginning_of_month(date) >> count
63
60
  end
64
61
  end
65
62
  end
@@ -20,7 +20,7 @@ module PartitionGardener
20
20
  segments = strategy.attached_tail_segments
21
21
  return [] if segments.empty?
22
22
 
23
- sorted = segments.sort_by { |segment| [segment.range_start.to_s, segment.name] }
23
+ sorted = segments.sort_by { |segment| [segment.range_start, segment.name] }
24
24
  gaps = []
25
25
 
26
26
  sorted.each_cons(2) do |left, right|
@@ -175,11 +175,13 @@ module PartitionGardener
175
175
  conflict_match = conflict_key.map { |column| "d.#{connection.quote_column_name(column)} = s.#{connection.quote_column_name(column)}" }.join(" AND ")
176
176
  update_columns = insert_columns - conflict_key
177
177
  update_clause = update_columns.map { |column| "#{connection.quote_column_name(column)} = EXCLUDED.#{connection.quote_column_name(column)}" }.join(", ")
178
- stale_check = if partitioned_columns.include?(stale_column)
179
- "d.#{connection.quote_column_name(stale_column)} >= s.#{connection.quote_column_name(stale_column)}"
180
- else
181
- "FALSE"
178
+ unless current_columns.include?(stale_column) && partitioned_columns.include?(stale_column)
179
+ raise ArgumentError,
180
+ "sync_stale_column #{stale_column.inspect} is missing from source or target; " \
181
+ "refusing unbounded delta sync"
182
182
  end
183
+
184
+ stale_check = "d.#{connection.quote_column_name(stale_column)} >= s.#{connection.quote_column_name(stale_column)}"
183
185
  window_predicate = [
184
186
  "#{current_partition_key_expression} >= #{start_date_sql}",
185
187
  "AND #{current_partition_key_expression} < #{end_date_sql}",
@@ -209,16 +211,11 @@ module PartitionGardener
209
211
  <<~SQL.chomp
210
212
  ON CONFLICT (#{conflict_key_str}) DO NOTHING
211
213
  SQL
212
- elsif partitioned_columns.include?(stale_column)
213
- <<~SQL.chomp
214
- ON CONFLICT (#{conflict_key_str}) DO UPDATE SET
215
- #{update_clause}
216
- WHERE #{quoted_table(target_table)}.#{connection.quote_column_name(stale_column)} < EXCLUDED.#{connection.quote_column_name(stale_column)}
217
- SQL
218
214
  else
219
215
  <<~SQL.chomp
220
216
  ON CONFLICT (#{conflict_key_str}) DO UPDATE SET
221
217
  #{update_clause}
218
+ WHERE #{quoted_table(target_table)}.#{connection.quote_column_name(stale_column)} < EXCLUDED.#{connection.quote_column_name(stale_column)}
222
219
  SQL
223
220
  end
224
221
 
@@ -406,17 +403,29 @@ module PartitionGardener
406
403
  def rename_partition_children!(parent_table, from_prefix, to_prefix)
407
404
  fetch_partitions(parent_table).each do |partition|
408
405
  partition_name = partition.to_s
409
- next if partition_name.include?(to_prefix)
406
+ bare_name = partition_name.delete('"').split(".").last
407
+ next unless bare_name.include?(from_prefix)
410
408
 
411
- new_partition_name = partition_name.gsub(from_prefix, to_prefix)
412
- run_sql "ALTER TABLE #{partition} RENAME TO #{new_partition_name}"
413
- say "Renamed #{partition} to #{new_partition_name}"
409
+ new_bare_name = bare_name.sub(from_prefix, to_prefix)
410
+ next if new_bare_name == bare_name
411
+
412
+ run_sql "ALTER TABLE #{partition} RENAME TO #{connection.quote_column_name(new_bare_name)}"
413
+ say "Renamed #{partition} to #{new_bare_name}"
414
414
  end
415
415
  end
416
416
 
417
417
  def repoint_serial_sequences!(table_name, sequence_pairs)
418
418
  sequence_pairs.each do |column_name, sequence_name|
419
- run_sql "ALTER SEQUENCE #{sequence_name} OWNED BY #{quoted_table(table_name)}.#{connection.quote_column_name(column_name)}"
419
+ quoted_column = connection.quote_column_name(column_name)
420
+ live_table = quoted_table(table_name)
421
+ run_sql <<~SQL
422
+ ALTER TABLE #{live_table}
423
+ ALTER COLUMN #{quoted_column}
424
+ SET DEFAULT nextval(#{connection.quote(sequence_name)}::regclass)
425
+ SQL
426
+ run_sql "ALTER SEQUENCE #{sequence_name} OWNED BY #{live_table}.#{quoted_column}"
427
+ max_id = run_sql("SELECT COALESCE(MAX(#{quoted_column}), 1) AS max_id FROM #{live_table}").first["max_id"].to_i
428
+ run_sql "SELECT setval(#{connection.quote(sequence_name)}, #{[max_id, 1].max})"
420
429
  say "Repointed #{sequence_name} to #{table_name}.#{column_name}"
421
430
  end
422
431
  end
@@ -139,8 +139,10 @@ module PartitionGardener
139
139
  end
140
140
 
141
141
  def apply_resuming!(plan, operations, staging_name, record)
142
- unless record.phase_at_least?("detach")
143
- prepare_staging!(staging_name)
142
+ # RunRecord.start persists phase "detach" before work. Treat detach as done only once
143
+ # the record has advanced to "segments" or later.
144
+ unless record.phase_at_least?("segments")
145
+ prepare_staging!(staging_name) unless Connection.partition_exists?(staging_name)
144
146
  detach_managed_tail_partitions!(staging_name, skip_names: keep_names_from(operations))
145
147
  drain_default_tail_rows_into_staging!(staging_name)
146
148
  record = record.advance!("detach", staging_row_count: staging_row_count(staging_name))
@@ -185,11 +187,21 @@ module PartitionGardener
185
187
  segments.each do |segment|
186
188
  next if Connection.partition_attached?(table_name, segment.name)
187
189
 
190
+ if Connection.partition_exists?(segment.name)
191
+ raise StandardError,
192
+ "partition #{segment.name} exists but is not attached to #{table_name}; " \
193
+ "CREATE TABLE IF NOT EXISTS … PARTITION OF would no-op and leave rows stranded"
194
+ end
195
+
188
196
  @executor.create_partition(
189
197
  table_name,
190
198
  segment.name,
191
199
  segment.for_values_clause(strategy)
192
200
  )
201
+
202
+ unless Connection.partition_attached?(table_name, segment.name)
203
+ raise StandardError, "failed to attach planned partition #{segment.name} to #{table_name}"
204
+ end
193
205
  end
194
206
  end
195
207
 
@@ -205,8 +217,10 @@ module PartitionGardener
205
217
  operations.select { |operation| operation.action == :drop }.each do |operation|
206
218
  partition_name = operation.attached_segment.name
207
219
  next if Connection.partition_attached?(@config[:table_name], partition_name)
220
+ next unless Connection.partition_exists?(partition_name)
221
+ next unless Connection.count_rows_in_partition_table(partition_name).zero?
208
222
 
209
- @executor.drop_table(partition_name) if Connection.partition_exists?(partition_name)
223
+ @executor.drop_table(partition_name)
210
224
  end
211
225
  end
212
226
 
@@ -1,3 +1,3 @@
1
1
  module PartitionGardener
2
- VERSION = "0.3.6"
2
+ VERSION = "0.3.7"
3
3
  end
@@ -3,10 +3,10 @@ module PartitionGardener
3
3
 
4
4
  MOVE_BATCH_SIZE: Integer
5
5
  FUTURE_MONTH_PARTITION_ROW_THRESHOLD: Integer
6
- DEFAULT_STATEMENT_TIMEOUT: ActiveSupport::Duration
6
+ DEFAULT_STATEMENT_TIMEOUT: Integer
7
7
 
8
8
  def self.run!: (
9
- ?statement_timeout: ActiveSupport::Duration,
9
+ ?statement_timeout: Integer | Numeric,
10
10
  ?job_class_name: String,
11
11
  ?continue_on_error: bool,
12
12
  ?dry_run: bool,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: partition_gardener
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -278,6 +278,7 @@ files:
278
278
  - docs/changelogs/20260916144600_public-identifiers-contract.md
279
279
  - docs/changelogs/20261005135700_checkpoint-table-name.md
280
280
  - docs/changelogs/20261005141621_cut-patch-release-0-3-6.md
281
+ - docs/changelogs/20261021220000_cut-patch-release-0-3-7.md
281
282
  - docs/cli.md
282
283
  - docs/configuration.md
283
284
  - docs/cutover.md
@@ -296,6 +297,9 @@ files:
296
297
  - docs/pg_party_recipe.md
297
298
  - docs/related_postgres_tooling.md
298
299
  - docs/retention.md
300
+ - docs/reviews/20261021220000_engineering-audit-0-3-7.md
301
+ - docs/reviews/20261028075216_engineering-audit-release-preparation.md
302
+ - docs/reviews/20261029150855_engineering-audit-release-preparation.md
299
303
  - docs/schemas/partition_garden.schema.json
300
304
  - docs/schemas/plan_report.schema.json
301
305
  - docs/tooling_split.md