From 96ba1a3d1c14c128bbb4588977f4d234f8ccc79e Mon Sep 17 00:00:00 2001 From: Zaid-edge Date: Thu, 10 Sep 2026 20:10:14 +0500 Subject: [PATCH 1/4] fix: rename ace.yaml to ace.example.yaml so `config init` works in a checkout `./ace config init --path ace.yaml` failed with "config file already exists at ace.yaml (use --force to overwrite)" because the repo tracked a config file at that exact path, so the bootstrap flow documented in the README and docs/configuration.md could never run from a clone. Rename the tracked copy to ace.example.yaml and update only the references that resolve to the file itself: the Dockerfile build-context COPY (the container path /etc/ace/ace.yaml and ACE_CONFIG are unchanged), the integration TestMain config load, and the two documentation links. The remaining ace.yaml mentions across docs/ describe the user's runtime config, which keeps its name. Also ignore /ace.yaml and /pg_service.conf so the files the README tells you to generate are not committed back into the repo. Note: `./ace ` from a fresh checkout now exits with "config file 'ace.yaml' not found" until `ace config init` is run. The tracked file was previously being picked up as a working config by the working-directory probe in cmd/ace/main.go. Verified with the full CI regression suite (all 21 steps from .github/workflows/test.yml): 95 top-level tests, 218 subtests, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 +++ Dockerfile | 2 +- README.md | 2 +- ace.yaml => ace.example.yaml | 0 docs/configuration.md | 2 +- tests/integration/main_test.go | 2 +- 6 files changed, 7 insertions(+), 4 deletions(-) rename ace.yaml => ace.example.yaml (100%) diff --git a/.gitignore b/.gitignore index f03cd376..cc34382a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ tests/integration/ace_tasks.db # Added by goreleaser init: dist/ +# Local config generated by `ace config init` (the tracked template is ace.example.yaml) +/ace.yaml +/pg_service.conf diff --git a/Dockerfile b/Dockerfile index 101f70ca..c944e274 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ COPY --from=downloader --chown=nonroot:nonroot /opt/ace/ace /usr/local/bin/ace COPY --from=downloader /opt/ace/LICENSE /licenses/LICENSE COPY --from=downloader /opt/ace/README.md /licenses/README.md -COPY --chown=nonroot:nonroot ace.yaml /etc/ace/ace.yaml +COPY --chown=nonroot:nonroot ace.example.yaml /etc/ace/ace.yaml ENV ACE_CONFIG=/etc/ace/ace.yaml diff --git a/README.md b/README.md index 557c1180..0dc11e74 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Set the `default_cluster` key in `ace.yaml` to the cluster name you most frequen For detailed information about creating and modifying ACE configuration files, visit [here](/docs/configuration.md). -The [`ace.yaml` file](ace.yaml) defines default values used when executing ACE commands like `table-diff` or `mtree table-diff`. You can modify properties that influence ACE performance and execution like timeout values and certificate information. +The [`ace.example.yaml` file](ace.example.yaml) is a reference copy of the configuration, showing the default values used when executing ACE commands like `table-diff` or `mtree table-diff`. In your own `ace.yaml`, you can modify properties that influence ACE performance and execution like timeout values and certificate information. The `pg_service.conf` file contains cluster connection details that help ACE locate nodes. After creating the file: diff --git a/ace.yaml b/ace.example.yaml similarity index 100% rename from ace.yaml rename to ace.example.yaml diff --git a/docs/configuration.md b/docs/configuration.md index 46c54795..81df430e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ ACE first attempts to use the Postgres service file to resolve connection inform ## The ace.yaml file -The [`ace.yaml` file](https://github.com/pgEdge/ace/blob/main/ace.yaml) defines default values used when calling the ACE commands. The file contains properties that control the resources used by ACE commands; after creating the `ace.yaml` file, use your choice of editor to customize the properties for your system: +The [`ace.example.yaml` file](https://github.com/pgEdge/ace/blob/main/ace.example.yaml) is a reference copy of the configuration that ACE ships, showing the default values used when calling the ACE commands. The file contains properties that control the resources used by ACE commands; after creating your own `ace.yaml` file, use your choice of editor to customize the properties for your system: diff --git a/tests/integration/main_test.go b/tests/integration/main_test.go index 2860c85d..f393238f 100644 --- a/tests/integration/main_test.go +++ b/tests/integration/main_test.go @@ -377,7 +377,7 @@ func TestMain(m *testing.M) { ) tcLog.SetDefault(log.New(io.Discard, "", 0)) - if err := config.Init("../../ace.yaml"); err != nil { + if err := config.Init("../../ace.example.yaml"); err != nil { log.Fatalf("Failed to load config: %v", err) } From abeb23b1b2361143aa439780894ca5aca39cd5bf Mon Sep 17 00:00:00 2001 From: Zaid-edge Date: Thu, 10 Sep 2026 20:43:41 +0500 Subject: [PATCH 2/4] fix: reconcile ace.example.yaml with the template `config init` writes CodeRabbit flagged that ace.example.yaml documents defaults users do not actually get. The repo-root example and the template embedded in ConfigInitCLI (internal/cli/default_config.yaml) had drifted apart, and the README and docs/configuration.md both link to the example as the reference copy. The disagreements: - concurrency_factor: the example said 1, but 0.5 is the real default (the --concurrency-factor flag's Value, and the fallback in internal/jobs/config.go). docs/configuration.md already documented 0.5, so the example was the sole wrong source. - max_connections and the adaptive_drain_* pair: present in the example and in config.Config, absent from the template, so `ace config init` wrote a config missing tunables the docs describe. - taskstore_path: present in the template, absent from the example. Add the missing keys to the template and make ace.example.yaml a byte-identical copy of it, so the documented defaults and the generated ones are the same file. Add TestExampleConfigMatchesEmbeddedTemplate to keep them that way, and TestDefaultConfigTemplateParses to catch a template that no longer loads into config.Config or disagrees with the flag default. Full CI regression suite re-run after the concurrency_factor change, since the integration tests load ace.example.yaml: all 21 steps from .github/workflows/test.yml pass, 95 top-level tests, 218 subtests, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) --- ace.example.yaml | 3 +- internal/cli/default_config.yaml | 8 ++++ internal/cli/default_config_test.go | 59 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 internal/cli/default_config_test.go diff --git a/ace.example.yaml b/ace.example.yaml index 44d8fb91..f80dbe35 100644 --- a/ace.example.yaml +++ b/ace.example.yaml @@ -20,7 +20,7 @@ postgres: tcp_keepalives_count: 5 table_diff: - concurrency_factor: 1 + concurrency_factor: 0.5 max_diff_rows: 1000000 min_diff_block_size: 1 max_diff_block_size: 1000000 @@ -77,6 +77,7 @@ server: tls_key_file: "" client_crl_file: "" allowed_common_names: [] + taskstore_path: "" schedule_jobs: [] schedule_config: [] diff --git a/internal/cli/default_config.yaml b/internal/cli/default_config.yaml index f792a4c5..f80dbe35 100644 --- a/internal/cli/default_config.yaml +++ b/internal/cli/default_config.yaml @@ -28,6 +28,7 @@ table_diff: diff_batch_size: 1 max_diff_batch_size: 1000 compare_unit_size: 10000 + max_connections: 0 # max DB connections per node (0 = derive from concurrency factor) mtree: cdc: @@ -36,6 +37,13 @@ mtree: cdc_processing_timeout: 300 cdc_metadata_flush_seconds: 10 cdc_flush_batch_size: 10000 + # Escalate a table to a whole-tree rehash when a bounded drain sees more + # than max(adaptive_drain_min_changes, adaptive_drain_fraction * rows) + # UPDATE changes for it (inserts/deletes are always tracked individually, + # preserving block split/merge maintenance). Set adaptive_drain_fraction + # to -1 to disable. + adaptive_drain_fraction: 0.01 + adaptive_drain_min_changes: 1000 schema: "pgedge_ace" diff: diff --git a/internal/cli/default_config_test.go b/internal/cli/default_config_test.go new file mode 100644 index 00000000..5656f20b --- /dev/null +++ b/internal/cli/default_config_test.go @@ -0,0 +1,59 @@ +// /////////////////////////////////////////////////////////////////////////// +// +// # ACE - Active Consistency Engine +// +// Copyright (C) 2023 - 2026, pgEdge (https://www.pgedge.com/) +// +// This software is released under the PostgreSQL License: +// https://opensource.org/license/postgresql +// +// /////////////////////////////////////////////////////////////////////////// + +package cli + +import ( + "os" + "testing" + + "gopkg.in/yaml.v3" + + "github.com/pgedge/ace/pkg/config" +) + +// exampleConfigPath is the repo-root reference copy that the README and +// docs/configuration.md link to. It must stay byte-identical to the template +// embedded in ConfigInitCLI, otherwise the documented defaults are not the +// defaults users actually get from `ace config init`. +const exampleConfigPath = "../../ace.example.yaml" + +func TestExampleConfigMatchesEmbeddedTemplate(t *testing.T) { + example, err := os.ReadFile(exampleConfigPath) + if err != nil { + t.Fatalf("read %s: %v", exampleConfigPath, err) + } + + if string(example) != defaultConfigYAML { + t.Errorf( + "%s has drifted from internal/cli/default_config.yaml.\n"+ + "`ace config init` writes the embedded template, so the two must "+ + "match or the documented defaults are wrong.\n"+ + "Run: cp internal/cli/default_config.yaml ace.example.yaml", + exampleConfigPath, + ) + } +} + +// TestDefaultConfigTemplateParses guards against shipping a template that +// fails to load, which would break `ace config init` followed by any command. +func TestDefaultConfigTemplateParses(t *testing.T) { + var cfg config.Config + if err := yaml.Unmarshal([]byte(defaultConfigYAML), &cfg); err != nil { + t.Fatalf("default_config.yaml does not parse into config.Config: %v", err) + } + + // The template must agree with the concurrency-factor default advertised by + // the CLI flag; disagreement is the drift that shipped a documented "1". + if got, want := cfg.TableDiff.ConcurrencyFactor, 0.5; got != want { + t.Errorf("table_diff.concurrency_factor = %v, want %v (matches the --concurrency-factor flag default)", got, want) + } +} From ed37caaeea340710b780bad865a9ed7aaef2194a Mon Sep 17 00:00:00 2001 From: Zaid-edge Date: Mon, 14 Sep 2026 12:30:10 +0500 Subject: [PATCH 3/4] refactor: name the reference config ace.sample.yaml PostgreSQL ships its reference configuration as postgresql.conf.sample and pg_hba.conf.sample, so "sample" is the name people coming from PG will expect for ACE's tracked copy. Rename ace.example.yaml accordingly, on review feedback. Covers every reference to the file: the Dockerfile build-context COPY, the integration TestMain config load, the drift guard in internal/cli, the README and docs/configuration.md links, and the .gitignore comment. The Go identifiers move with it (sampleConfigPath, TestSampleConfigMatchesEmbeddedTemplate) so "example" is not left stranded in the code. The container path stays /etc/ace/ace.yaml and the runtime config users generate is still ace.yaml; only the tracked reference copy is renamed. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 2 +- Dockerfile | 2 +- README.md | 2 +- ace.example.yaml => ace.sample.yaml | 0 docs/configuration.md | 2 +- internal/cli/default_config_test.go | 16 ++++++++-------- tests/integration/main_test.go | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) rename ace.example.yaml => ace.sample.yaml (100%) diff --git a/.gitignore b/.gitignore index cc34382a..6dcbe262 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,6 @@ tests/integration/ace_tasks.db # Added by goreleaser init: dist/ -# Local config generated by `ace config init` (the tracked template is ace.example.yaml) +# Local config generated by `ace config init` (the tracked template is ace.sample.yaml) /ace.yaml /pg_service.conf diff --git a/Dockerfile b/Dockerfile index c944e274..51d9265e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ COPY --from=downloader --chown=nonroot:nonroot /opt/ace/ace /usr/local/bin/ace COPY --from=downloader /opt/ace/LICENSE /licenses/LICENSE COPY --from=downloader /opt/ace/README.md /licenses/README.md -COPY --chown=nonroot:nonroot ace.example.yaml /etc/ace/ace.yaml +COPY --chown=nonroot:nonroot ace.sample.yaml /etc/ace/ace.yaml ENV ACE_CONFIG=/etc/ace/ace.yaml diff --git a/README.md b/README.md index 0dc11e74..a4fc75c4 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Set the `default_cluster` key in `ace.yaml` to the cluster name you most frequen For detailed information about creating and modifying ACE configuration files, visit [here](/docs/configuration.md). -The [`ace.example.yaml` file](ace.example.yaml) is a reference copy of the configuration, showing the default values used when executing ACE commands like `table-diff` or `mtree table-diff`. In your own `ace.yaml`, you can modify properties that influence ACE performance and execution like timeout values and certificate information. +The [`ace.sample.yaml` file](ace.sample.yaml) is a reference copy of the configuration, showing the default values used when executing ACE commands like `table-diff` or `mtree table-diff`. In your own `ace.yaml`, you can modify properties that influence ACE performance and execution like timeout values and certificate information. The `pg_service.conf` file contains cluster connection details that help ACE locate nodes. After creating the file: diff --git a/ace.example.yaml b/ace.sample.yaml similarity index 100% rename from ace.example.yaml rename to ace.sample.yaml diff --git a/docs/configuration.md b/docs/configuration.md index 81df430e..839da01f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,7 +16,7 @@ ACE first attempts to use the Postgres service file to resolve connection inform ## The ace.yaml file -The [`ace.example.yaml` file](https://github.com/pgEdge/ace/blob/main/ace.example.yaml) is a reference copy of the configuration that ACE ships, showing the default values used when calling the ACE commands. The file contains properties that control the resources used by ACE commands; after creating your own `ace.yaml` file, use your choice of editor to customize the properties for your system: +The [`ace.sample.yaml` file](https://github.com/pgEdge/ace/blob/main/ace.sample.yaml) is a reference copy of the configuration that ACE ships, showing the default values used when calling the ACE commands. The file contains properties that control the resources used by ACE commands; after creating your own `ace.yaml` file, use your choice of editor to customize the properties for your system: diff --git a/internal/cli/default_config_test.go b/internal/cli/default_config_test.go index 5656f20b..d89258e0 100644 --- a/internal/cli/default_config_test.go +++ b/internal/cli/default_config_test.go @@ -20,25 +20,25 @@ import ( "github.com/pgedge/ace/pkg/config" ) -// exampleConfigPath is the repo-root reference copy that the README and +// sampleConfigPath is the repo-root reference copy that the README and // docs/configuration.md link to. It must stay byte-identical to the template // embedded in ConfigInitCLI, otherwise the documented defaults are not the // defaults users actually get from `ace config init`. -const exampleConfigPath = "../../ace.example.yaml" +const sampleConfigPath = "../../ace.sample.yaml" -func TestExampleConfigMatchesEmbeddedTemplate(t *testing.T) { - example, err := os.ReadFile(exampleConfigPath) +func TestSampleConfigMatchesEmbeddedTemplate(t *testing.T) { + sample, err := os.ReadFile(sampleConfigPath) if err != nil { - t.Fatalf("read %s: %v", exampleConfigPath, err) + t.Fatalf("read %s: %v", sampleConfigPath, err) } - if string(example) != defaultConfigYAML { + if string(sample) != defaultConfigYAML { t.Errorf( "%s has drifted from internal/cli/default_config.yaml.\n"+ "`ace config init` writes the embedded template, so the two must "+ "match or the documented defaults are wrong.\n"+ - "Run: cp internal/cli/default_config.yaml ace.example.yaml", - exampleConfigPath, + "Run: cp internal/cli/default_config.yaml ace.sample.yaml", + sampleConfigPath, ) } } diff --git a/tests/integration/main_test.go b/tests/integration/main_test.go index f393238f..5893bad2 100644 --- a/tests/integration/main_test.go +++ b/tests/integration/main_test.go @@ -377,7 +377,7 @@ func TestMain(m *testing.M) { ) tcLog.SetDefault(log.New(io.Discard, "", 0)) - if err := config.Init("../../ace.example.yaml"); err != nil { + if err := config.Init("../../ace.sample.yaml"); err != nil { log.Fatalf("Failed to load config: %v", err) } From cbb9d48646c704d049a3f4adeb07437c492efa1e Mon Sep 17 00:00:00 2001 From: Zaid-edge Date: Mon, 14 Sep 2026 13:08:48 +0500 Subject: [PATCH 4/4] docs: document the config drift guard and the integration TestMain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit's docstring check measured 33.33% on the functions this branch touches: of the three it analysed, only TestDefaultConfigTemplateParses carried a doc comment. TestSampleConfigMatchesEmbeddedTemplate's rationale was there all along but sat on the sampleConfigPath const rather than on the test, so move it to the function and leave the const with the one line that describes it. TestMain had no comment at all; note what it stands up, since the shared cluster and tables it creates are what every test in the package compares against. Comments only — no change to behaviour. Co-Authored-By: Claude Opus 5 (1M context) --- internal/cli/default_config_test.go | 10 ++++++---- tests/integration/main_test.go | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/cli/default_config_test.go b/internal/cli/default_config_test.go index d89258e0..4725b4d8 100644 --- a/internal/cli/default_config_test.go +++ b/internal/cli/default_config_test.go @@ -20,12 +20,14 @@ import ( "github.com/pgedge/ace/pkg/config" ) -// sampleConfigPath is the repo-root reference copy that the README and -// docs/configuration.md link to. It must stay byte-identical to the template -// embedded in ConfigInitCLI, otherwise the documented defaults are not the -// defaults users actually get from `ace config init`. +// sampleConfigPath is the repo-root reference copy the README and +// docs/configuration.md link to. const sampleConfigPath = "../../ace.sample.yaml" +// TestSampleConfigMatchesEmbeddedTemplate keeps ace.sample.yaml byte-identical +// to the template embedded in ConfigInitCLI. `ace config init` writes the +// template, so any drift means the documented defaults are not the ones users +// actually get. func TestSampleConfigMatchesEmbeddedTemplate(t *testing.T) { sample, err := os.ReadFile(sampleConfigPath) if err != nil { diff --git a/tests/integration/main_test.go b/tests/integration/main_test.go index 5893bad2..5b3bbc42 100644 --- a/tests/integration/main_test.go +++ b/tests/integration/main_test.go @@ -370,6 +370,9 @@ func teardownPostgresCluster(t *testing.T) { } } +// TestMain loads the ACE config, then brings up the three-node spock cluster +// and the shared customers tables that every test in this package compares +// against, and tears the cluster down once the run finishes. func TestMain(m *testing.M) { os.Setenv( "TESTCONTAINERS_RYUK_DISABLED",