cem_acpt 0.11.0 → 0.11.2

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.worktreeinclude +1 -0
  4. data/CLAUDE.md +64 -25
  5. data/Gemfile.lock +1 -1
  6. data/README.md +20 -7
  7. data/docs/ARCHITECTURE.md +1042 -0
  8. data/docs/rfcs/0000-template.md +54 -0
  9. data/docs/rfcs/0001-fix-bolt-missing-skip-path.md +105 -0
  10. data/docs/rfcs/0002-fix-default-character-substitutions.md +119 -0
  11. data/docs/rfcs/0003-windows-image-builder-template.md +110 -0
  12. data/docs/rfcs/0004-image-name-truncation-off-by-one.md +108 -0
  13. data/docs/rfcs/0005-os-dispatch-replace-windows-heuristic.md +117 -0
  14. data/docs/rfcs/0006-configurable-windows-bucket.md +96 -0
  15. data/docs/rfcs/0007-logging-quiet-and-typos.md +121 -0
  16. data/docs/rfcs/0008-namespace-platform-classes.md +110 -0
  17. data/docs/rfcs/0009-bolt-log-formatter-cleanup.md +111 -0
  18. data/docs/rfcs/0010-dead-code-cleanup.md +83 -0
  19. data/docs/rfcs/0011-provisioner-factory-consistency.md +89 -0
  20. data/docs/rfcs/README.md +34 -0
  21. data/lib/cem_acpt/cli.rb +10 -1
  22. data/lib/cem_acpt/config/cem_acpt.rb +4 -1
  23. data/lib/cem_acpt/image_builder/errors.rb +24 -0
  24. data/lib/cem_acpt/image_builder/provision_commands.rb +15 -3
  25. data/lib/cem_acpt/image_builder.rb +29 -2
  26. data/lib/cem_acpt/image_name_builder.rb +8 -1
  27. data/lib/cem_acpt/platform/gcp.rb +112 -106
  28. data/lib/cem_acpt/platform.rb +21 -19
  29. data/lib/cem_acpt/provision/terraform/linux.rb +1 -1
  30. data/lib/cem_acpt/provision/terraform/os_data.rb +23 -0
  31. data/lib/cem_acpt/provision/terraform/windows.rb +7 -1
  32. data/lib/cem_acpt/provision/terraform.rb +20 -16
  33. data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +2 -1
  34. data/lib/cem_acpt/test_runner/log_formatter.rb +0 -1
  35. data/lib/cem_acpt/test_runner.rb +21 -8
  36. data/lib/cem_acpt/utils/winrm_runner.rb +4 -3
  37. data/lib/cem_acpt/utils.rb +0 -12
  38. data/lib/cem_acpt/version.rb +1 -1
  39. data/lib/cem_acpt.rb +19 -7
  40. data/lib/terraform/gcp/linux/main.tf +6 -1
  41. data/lib/terraform/image/gcp/linux/main.tf +8 -1
  42. data/specifications/CEM-6713.md +165 -0
  43. data/specifications/CEM-6714.md +271 -0
  44. data/specifications/CEM-6715.md +133 -0
  45. data/specifications/CEM-6716.md +160 -0
  46. data/specifications/CEM-6717.md +239 -0
  47. data/specifications/CEM-6718.md +120 -0
  48. data/specifications/CEM-6719.md +173 -0
  49. metadata +26 -11
  50. data/.claude/settings.local.json +0 -7
  51. data/lib/cem_acpt/action_result.rb +0 -91
  52. data/lib/cem_acpt/puppet_helpers.rb +0 -38
  53. data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +0 -65
  54. data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +0 -54
@@ -0,0 +1,96 @@
1
+ # RFC 0006: Make the Windows GCS bucket configurable
2
+
3
+ - **Status:** Proposed
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** High
7
+ - **Category:** Bug
8
+ - **Affected components:** `lib/cem_acpt/test_runner.rb`, `lib/cem_acpt/config/cem_acpt.rb`
9
+
10
+ ## Summary
11
+
12
+ The Windows test path uploads the Puppet module tarball to a
13
+ hard-coded `gs://win_cem_acpt` bucket. Anyone who is not a Puppet
14
+ employee (or who has a different bucket name in their own GCP project)
15
+ must fork the gem to use the Windows path. Make the bucket name
16
+ configurable.
17
+
18
+ ## Background
19
+
20
+ `lib/cem_acpt/test_runner.rb:386`:
21
+
22
+ ```ruby
23
+ @run_data[:win_remote_module_path] =
24
+ File.join('gs://win_cem_acpt', @run_data[:win_remote_module_name])
25
+ ```
26
+
27
+ The same string appears in `cleanup_bucket` (line 397+).
28
+ [`docs/ARCHITECTURE.md` §13](../ARCHITECTURE.md#13-windows-path) calls
29
+ this out as not configurable.
30
+
31
+ ## Problem
32
+
33
+ - External users cannot run the Windows acceptance suite at all.
34
+ - There is no way to point at a per-project bucket without editing
35
+ vendored gem source.
36
+ - A typo or bucket-name change requires a new gem release.
37
+
38
+ ## Proposal
39
+
40
+ Add a config key `platform.gcp.windows_bucket` (under the existing
41
+ `platform` namespace), defaulting to `win_cem_acpt` to preserve
42
+ current behavior for Puppet contributors:
43
+
44
+ ```ruby
45
+ # lib/cem_acpt/config/cem_acpt.rb defaults
46
+ platform: {
47
+ name: 'gcp',
48
+ gcp: {
49
+ windows_bucket: 'win_cem_acpt',
50
+ },
51
+ },
52
+ ```
53
+
54
+ In `test_runner.rb`, replace literals with config lookups:
55
+
56
+ ```ruby
57
+ def windows_bucket_uri
58
+ bucket = config.get('platform.gcp.windows_bucket')
59
+ raise 'platform.gcp.windows_bucket is not configured' if bucket.nil? || bucket.empty?
60
+ "gs://#{bucket}"
61
+ end
62
+ ```
63
+
64
+ Use `windows_bucket_uri` in both `upload_module_to_bucket` and
65
+ `cleanup_bucket`.
66
+
67
+ Also surface a `--windows-bucket BUCKET` CLI flag for ad-hoc overrides
68
+ that mirror existing flag-based knobs.
69
+
70
+ ## Alternatives Considered
71
+
72
+ - **Derive the bucket name from `platform.project`.** Avoids a new
73
+ config key but requires every project to follow the same naming
74
+ convention, which is its own coupling.
75
+ - **Drop the bucket-upload step entirely** in favor of a WinRM file
76
+ push. Larger change; would also remove a useful "the module
77
+ artifact is reachable from any node" property.
78
+
79
+ ## Risks & Migration
80
+
81
+ - Defaulting to `win_cem_acpt` means existing internal users see no
82
+ change.
83
+ - External users gain a new required override (or use the default
84
+ if their bucket happens to share the name).
85
+ - Config-explanation output (`-X`) will start listing the new key.
86
+
87
+ ## Acceptance Criteria
88
+
89
+ - [ ] Config schema (`VALID_KEYS` and `defaults`) extended.
90
+ - [ ] CLI flag `--windows-bucket` parsed in `Cli`.
91
+ - [ ] All references to `'gs://win_cem_acpt'` removed from
92
+ `test_runner.rb`.
93
+ - [ ] RSpec coverage for both upload and cleanup paths.
94
+ - [ ] [`docs/ARCHITECTURE.md` §13](../ARCHITECTURE.md#13-windows-path)
95
+ updated to describe the new key.
96
+ - [ ] README updated.
@@ -0,0 +1,121 @@
1
+ # RFC 0007: Tighten `--quiet` semantics and fix logging typos
2
+
3
+ - **Status:** Proposed
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** High
7
+ - **Category:** Bug
8
+ - **Affected components:** `lib/cem_acpt.rb`, `lib/cem_acpt/test_runner.rb`
9
+
10
+ ## Summary
11
+
12
+ `--quiet` is silently a no-op when used without `--log-file`, and a
13
+ log line in `upload_module_to_bucket` references a non-existent
14
+ `@run_data[:module_pakage_path]` key (typo for `module_package_path`).
15
+ Fix both. Document the CI-mode override.
16
+
17
+ ## Background
18
+
19
+ `lib/cem_acpt.rb#initialize_logger!` (lines 59-80):
20
+
21
+ ```ruby
22
+ logdevs = [$stdout]
23
+ if config.get('log_file') && config.get('quiet')
24
+ logdevs = [config.get('log_file')]
25
+ elsif config.get('log_file') && !config.get('quiet')
26
+ logdevs << config.get('log_file')
27
+ end
28
+ if config.ci_mode? && !logdevs.include?($stdout)
29
+ logdevs << $stdout
30
+ end
31
+ ```
32
+
33
+ [`docs/ARCHITECTURE.md` §15 / §19 (item 6)](../ARCHITECTURE.md#15-logging)
34
+ describes the current behavior and flags the surprising "quiet alone
35
+ does nothing" case.
36
+
37
+ `test_runner.rb:388`:
38
+
39
+ ```ruby
40
+ logger.info('CemAcpt') {
41
+ "Uploading #{@run_data[:module_pakage_path]} to #{@run_data[:win_remote_module_path]}..."
42
+ }
43
+ ```
44
+
45
+ `@run_data[:module_pakage_path]` is never assigned. The interpolation
46
+ quietly produces an empty string in the log. The other call on the
47
+ following line correctly reads `module_package_path`.
48
+
49
+ ## Problem
50
+
51
+ 1. `--quiet` without `--log-file` reads as "be quiet" but does
52
+ nothing — confusing for anyone scripting around it.
53
+ 2. `--quiet --log-file X` *with* CI mode also adds `$stdout` back in;
54
+ undocumented in `--help`.
55
+ 3. Misspelled run-data key produces a broken log line — no functional
56
+ damage today, but the log message is one of the few breadcrumbs
57
+ for diagnosing the Windows upload step.
58
+
59
+ ## Proposal
60
+
61
+ ### Quiet semantics
62
+
63
+ Treat `--quiet` as "drop `$stdout`" everywhere except CI mode, with
64
+ clear messaging:
65
+
66
+ ```ruby
67
+ def initialize_logger!
68
+ raise 'Config must be loaded ...' if config.nil? || config.empty?
69
+
70
+ logdevs = []
71
+ logdevs << $stdout unless config.get('quiet')
72
+ logdevs << config.get('log_file') if config.get('log_file')
73
+ if config.ci_mode? && !logdevs.include?($stdout)
74
+ logger_warn_once('--quiet is overridden in CI mode; logging to stdout for ::group:: support')
75
+ logdevs << $stdout
76
+ end
77
+ raise '--quiet without --log-file would silence all output; pass --log-file or drop --quiet' if logdevs.empty?
78
+ ...
79
+ end
80
+ ```
81
+
82
+ Refusing the all-silenced combination outside CI is preferred to
83
+ silently dropping logs.
84
+
85
+ ### Typo
86
+
87
+ In `test_runner.rb:388`, change `:module_pakage_path` to
88
+ `:module_package_path`. Add a smoke test that exercises
89
+ `upload_module_to_bucket` with a stub shell to assert the log line
90
+ contains the correct path.
91
+
92
+ ### Documentation
93
+
94
+ Update `docs/ARCHITECTURE.md` §15 to reflect the new behavior, and
95
+ add a one-line note in `Cli` help next to `--quiet`.
96
+
97
+ ## Alternatives Considered
98
+
99
+ - **Leave `--quiet` alone.** Documented as "no effect alone" — but
100
+ that's exactly the surprise we want to remove.
101
+ - **Promote `--quiet` to fully silence logs even in CI.** Breaks
102
+ `::group::` annotations on Actions; not desirable.
103
+
104
+ ## Risks & Migration
105
+
106
+ - Users today who passed `--quiet` and expected stdout to remain will
107
+ now see stdout disappear (correctly). This is a deliberate
108
+ behavioral fix.
109
+ - Users today who passed `--quiet` with no `--log-file` get an
110
+ immediate, clear error rather than running normally with stdout
111
+ intact.
112
+
113
+ ## Acceptance Criteria
114
+
115
+ - [ ] `lib/cem_acpt.rb#initialize_logger!` updated; comment cites
116
+ this RFC.
117
+ - [ ] `:module_pakage_path` typo fixed.
118
+ - [ ] RSpec covers the four logging combinations (`-q`, `-q --log-file`,
119
+ `--log-file` alone, `-q` in CI mode).
120
+ - [ ] `docs/ARCHITECTURE.md` §15 updated.
121
+ - [ ] `--help` output mentions the CI override.
@@ -0,0 +1,110 @@
1
+ # RFC 0008: Namespace dynamically-created platform classes
2
+
3
+ - **Status:** Implemented (CEM-6717)
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** Medium
7
+ - **Category:** Refactor
8
+ - **Affected components:** `lib/cem_acpt/platform.rb`, `lib/cem_acpt/platform/gcp.rb`
9
+
10
+ ## Summary
11
+
12
+ `Platform.platform_class` registers each generated class as a
13
+ **top-level** constant (e.g. `::Gcp`). This pollutes `Object`'s
14
+ constant table and risks silent collisions with any same-named class
15
+ introduced elsewhere — including by transitive dependencies. Move the
16
+ generated class into the `CemAcpt::Platform` namespace.
17
+
18
+ ## Background
19
+
20
+ `lib/cem_acpt/platform.rb:79-115`:
21
+
22
+ ```ruby
23
+ def platform_class(base_type, platform)
24
+ class_name = platform.capitalize
25
+ ...
26
+ if Object.const_defined?(class_name)
27
+ klass = Object.const_get(class_name)
28
+ else
29
+ ...
30
+ klass = Object.const_set(class_name, Class.new(baseklass) do
31
+ require_relative "platform/#{platform}"
32
+ include Platform
33
+ end)
34
+ end
35
+ klass
36
+ end
37
+ ```
38
+
39
+ [`docs/ARCHITECTURE.md` §6 / §19 (item 5)](../ARCHITECTURE.md#6-platform-abstraction)
40
+ flags this as a defensive-namespace concern.
41
+
42
+ ## Problem
43
+
44
+ - A future contributor (or a gem we depend on) defining
45
+ `class ::Gcp` would silently win the cache check
46
+ `Object.const_defined?('Gcp')`, so `platform_class` would return
47
+ the wrong class.
48
+ - The constant lookup is also case-sensitive and the cache key
49
+ `platform.capitalize` fails for multi-word platforms like
50
+ `aws_govcloud` (would become `Aws_govcloud`).
51
+ - Top-level constants make the runtime class graph noisier than it
52
+ needs to be.
53
+
54
+ ## Proposal
55
+
56
+ Register and look up the class under `CemAcpt::Platform`:
57
+
58
+ ```ruby
59
+ def platform_class(base_type, platform)
60
+ const_name = platform.split(/[_-]/).map(&:capitalize).join
61
+
62
+ if CemAcpt::Platform.const_defined?(const_name, false)
63
+ return CemAcpt::Platform.const_get(const_name, false)
64
+ end
65
+
66
+ baseklass = case base_type.to_sym
67
+ when :base then CemAcpt::Platform::Base
68
+ when :test then CemAcpt::Platform::TestBase
69
+ else raise Error, "Base type #{base_type} is not supported"
70
+ end
71
+
72
+ CemAcpt::Platform.const_set(const_name, Class.new(baseklass) do
73
+ require_relative "platform/#{platform}"
74
+ include CemAcpt::Platform.const_get(:Platform, false) # documented hook
75
+ end)
76
+ end
77
+ ```
78
+
79
+ Notes:
80
+
81
+ - `const_defined?(name, false)` skips ancestor lookup, eliminating the
82
+ collision.
83
+ - The `split(...).map(&:capitalize).join` form camel-cases multi-word
84
+ platform names safely.
85
+ - `include Platform` references the module from the loaded file; we
86
+ preserve that contract but resolve it relative to the new namespace.
87
+
88
+ ## Alternatives Considered
89
+
90
+ - **Switch to a registration DSL** (`Platform.register :gcp do ... end`).
91
+ Cleaner long term but a wider refactor. Defer.
92
+ - **Keep top-level but assert uniqueness.** Solves the collision
93
+ problem but doesn't address the namespace pollution.
94
+
95
+ ## Risks & Migration
96
+
97
+ - Any code that referenced the top-level `::Gcp` (none in this repo
98
+ per grep) would need to change. There are no such references in
99
+ `lib/`, `exe/`, or `spec/`.
100
+ - The base class `CemAcpt::Platform::Base` remains untouched.
101
+
102
+ ## Acceptance Criteria
103
+
104
+ - [ ] `Platform.platform_class` registers under `CemAcpt::Platform`.
105
+ - [ ] No top-level `::Gcp` constant is created at runtime
106
+ (verified via spec).
107
+ - [ ] Multi-word platform names (e.g. `azure_arm`) round-trip
108
+ correctly.
109
+ - [ ] [`docs/ARCHITECTURE.md` §6](../ARCHITECTURE.md#6-platform-abstraction)
110
+ and §19 updated.
@@ -0,0 +1,111 @@
1
+ # RFC 0009: Wire up or remove the orphaned Bolt log formatters
2
+
3
+ - **Status:** Proposed
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** Medium
7
+ - **Category:** Cleanup
8
+ - **Affected components:** `lib/cem_acpt/test_runner/log_formatter.rb`, `lib/cem_acpt/test_runner/log_formatter/`
9
+
10
+ ## Summary
11
+
12
+ Two formatter files exist under `lib/cem_acpt/test_runner/log_formatter/`
13
+ but are never matched by `LogFormatter.new_formatter`'s `case`.
14
+ `bolt_output_formatter.rb` is `require_relative`'d but unused;
15
+ `bolt_error_formatter.rb` is neither required nor referenced. Either
16
+ they should be hooked into the dispatcher or deleted.
17
+
18
+ ## Background
19
+
20
+ `lib/cem_acpt/test_runner/log_formatter.rb`:
21
+
22
+ ```ruby
23
+ require_relative 'log_formatter/bolt_summary_results_formatter'
24
+ require_relative 'log_formatter/bolt_output_formatter' # required, not matched
25
+ require_relative 'log_formatter/goss_action_response'
26
+ require_relative 'log_formatter/goss_error_formatter'
27
+ require_relative 'log_formatter/standard_error_formatter'
28
+ # bolt_error_formatter.rb is NOT required
29
+
30
+ module ...
31
+ def self.new_formatter(result, *args, **_kwargs)
32
+ case result
33
+ when CemAcpt::Goss::Api::ActionResponse then ...
34
+ when CemAcpt::Bolt::SummaryResults then BoltSummaryResultsFormatter.new(...)
35
+ when StandardError then StandardErrorFormatter.new(result)
36
+ else raise ArgumentError, ...
37
+ end
38
+ end
39
+ end
40
+ ```
41
+
42
+ [`docs/ARCHITECTURE.md` §11 / §19 (item 8)](../ARCHITECTURE.md#11-result-aggregation--log-formatting)
43
+ flags both files.
44
+
45
+ ## Problem
46
+
47
+ - Future contributors waste time deciding whether the formatters are
48
+ load-bearing. The `require_relative` for `bolt_output_formatter`
49
+ creates the false impression that they are.
50
+ - If `BoltOutputFormatter` was meant to render
51
+ `Bolt::Cmd::Output` instances, the dispatcher silently falls through
52
+ to `StandardErrorFormatter` (or raises) for any caller that pushes
53
+ one onto the results queue.
54
+
55
+ ## Proposal
56
+
57
+ Pick one of two paths after a 5-minute decision:
58
+
59
+ ### Option A — wire them up
60
+
61
+ If `BoltOutputFormatter` / `BoltErrorFormatter` are intended to render
62
+ `Bolt::Cmd::Output` and `Bolt::Cmd::OutputError`:
63
+
64
+ ```ruby
65
+ require_relative 'log_formatter/bolt_error_formatter'
66
+
67
+ case result
68
+ ...
69
+ when CemAcpt::Bolt::Cmd::OutputError
70
+ BoltErrorFormatter.new(result)
71
+ when CemAcpt::Bolt::Cmd::Output
72
+ BoltOutputFormatter.new(*args, subject: result)
73
+ when CemAcpt::Bolt::SummaryResults
74
+ BoltSummaryResultsFormatter.new(*args, subject: result)
75
+ ...
76
+ end
77
+ ```
78
+
79
+ Add coverage in
80
+ `spec/cem_acpt/test_runner/log_formatter/` mirroring the existing
81
+ `goss_action_response_spec.rb`.
82
+
83
+ ### Option B — delete them
84
+
85
+ If, as the current code suggests, only the summary formatter is
86
+ needed, delete both files and the dangling `require_relative`. Add
87
+ a one-line comment on `BoltSummaryResultsFormatter` documenting that
88
+ it is the canonical Bolt formatter.
89
+
90
+ ## Recommendation
91
+
92
+ Option B. The dispatcher has been shipping in this state for at least
93
+ several releases without anyone noticing the absent cases — the
94
+ formatters are aspirational dead code. Deleting them now removes the
95
+ ambiguity; if the need re-emerges, reintroducing them is a small
96
+ patch.
97
+
98
+ ## Risks & Migration
99
+
100
+ - Public API: none. The formatter classes are not part of the gem's
101
+ documented API.
102
+ - Tests: none of the existing specs reference either file.
103
+
104
+ ## Acceptance Criteria
105
+
106
+ - [ ] Decision recorded (Option A or B).
107
+ - [ ] If A: dispatcher updated, both files required, RSpec coverage
108
+ added.
109
+ - [ ] If B: both files deleted, dangling `require_relative` removed.
110
+ - [ ] [`docs/ARCHITECTURE.md` §11](../ARCHITECTURE.md#11-result-aggregation--log-formatting)
111
+ and §19 updated.
@@ -0,0 +1,83 @@
1
+ # RFC 0010: Remove unreferenced top-level helpers and Windows placeholder
2
+
3
+ - **Status:** Proposed
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** Medium
7
+ - **Category:** Cleanup
8
+ - **Affected components:** `lib/cem_acpt/puppet_helpers.rb`, `lib/cem_acpt/action_result.rb`, `lib/cem_acpt/provision/terraform/windows.rb`, `lib/cem_acpt/utils.rb`
9
+
10
+ ## Summary
11
+
12
+ Several files / methods are dead — defined, but never called from
13
+ `lib/`, `exe/`, or `spec/`. They confuse the codebase map and
14
+ contradict the actual architecture. Remove them.
15
+
16
+ ## Background
17
+
18
+ [`docs/ARCHITECTURE.md` §19 (items 1, 2, 3)](../ARCHITECTURE.md#19-open-questions--observed-dead-code)
19
+ identifies the following:
20
+
21
+ | File / Symbol | Status |
22
+ |-------------------------------------------------------------|----------------------------------------------|
23
+ | `lib/cem_acpt/puppet_helpers.rb` (`PuppetHelpers::Module.build_module_package`) | Superseded by `Utils::Puppet::ModulePackageBuilder`. No callers. |
24
+ | `lib/cem_acpt/action_result.rb` (`CemAcpt::ActionResult`, `CemAcpt::ErrorActionResult`) | Superseded by `TestRunner::TestResults::TestActionResult` / `TestErrorActionResult`. No callers. |
25
+ | `Provision::Windows#provision_commands` returning `['placeholder']` | Inert; the Windows path runs PowerShell from Ruby, not from a `remote-exec`. |
26
+ | `Utils.package_win_module` | Referenced by ARCHITECTURE.md as unused; re-confirm before removing. |
27
+
28
+ ## Problem
29
+
30
+ - Each of these reads as "load-bearing" to a contributor wandering
31
+ the tree.
32
+ - The placeholder return value in `Provision::Windows` is the kind of
33
+ code that gets "fixed" by a future contributor who doesn't realize
34
+ the Windows path skips the `remote-exec` block entirely.
35
+ - `action_result.rb` defines two top-level constants
36
+ (`CemAcpt::ActionResult`, `CemAcpt::ErrorActionResult`) that shadow
37
+ intuitive names a future contributor might want.
38
+
39
+ ## Proposal
40
+
41
+ 1. **Delete** `lib/cem_acpt/puppet_helpers.rb`. Audit specs/fixtures
42
+ first; remove any orphan requires.
43
+ 2. **Delete** `lib/cem_acpt/action_result.rb`. Same audit.
44
+ 3. **In `Provision::Windows`**, replace the placeholder method with
45
+ a comment documenting that the Windows path intentionally has no
46
+ `remote-exec` provisioner, and have `provision_commands` raise
47
+ `NotImplementedError` if called by accident:
48
+
49
+ ```ruby
50
+ def provision_commands
51
+ raise NotImplementedError,
52
+ 'Windows provisioning is performed via Utils::WinRMRunner, ' \
53
+ 'not via Terraform remote-exec. See ARCHITECTURE.md §13.'
54
+ end
55
+ ```
56
+
57
+ 4. **`Utils.package_win_module`**: confirm with `git grep` and remove
58
+ if there are no callers. (One pass at the start of this RFC's
59
+ implementation should suffice.)
60
+
61
+ ## Alternatives Considered
62
+
63
+ - **Mark as deprecated and keep around for one release.** Overkill —
64
+ these are internal helpers, not public API.
65
+ - **Move to a `legacy/` directory.** Just delays the cleanup.
66
+
67
+ ## Risks & Migration
68
+
69
+ - These symbols are not part of the public API of the gem; removing
70
+ them does not require a major version bump.
71
+ - A `git grep` for each symbol must come back empty before deletion.
72
+
73
+ ## Acceptance Criteria
74
+
75
+ - [ ] `git grep PuppetHelpers` returns no matches under `lib/`,
76
+ `exe/`, or `spec/`; file deleted.
77
+ - [ ] `git grep CemAcpt::ActionResult` returns no matches; file
78
+ deleted.
79
+ - [ ] `Provision::Windows#provision_commands` no longer returns
80
+ `['placeholder']`.
81
+ - [ ] `Utils.package_win_module` removed if confirmed unused.
82
+ - [ ] [`docs/ARCHITECTURE.md` §19](../ARCHITECTURE.md#19-open-questions--observed-dead-code)
83
+ items 1, 2, 3 deleted.
@@ -0,0 +1,89 @@
1
+ # RFC 0011: Make the provisioner factory honor configuration
2
+
3
+ - **Status:** Proposed
4
+ - **Author:** TBD
5
+ - **Created:** 2026-04-30
6
+ - **Priority:** Low
7
+ - **Category:** Refactor
8
+ - **Affected components:** `lib/cem_acpt/config/base.rb`, `lib/cem_acpt/provision.rb`
9
+
10
+ ## Summary
11
+
12
+ `Config::Base#add_static_options!` force-sets `provisioner =
13
+ 'terraform'` after every other config source has been merged. The
14
+ `Provision.new_provisioner` factory still branches on
15
+ `config.get('provisioner')` and raises for unknown values — so the
16
+ factory looks pluggable but is not. Either honor the config or remove
17
+ the factory.
18
+
19
+ ## Background
20
+
21
+ [`docs/ARCHITECTURE.md` §7 / §19 (item 4)](../ARCHITECTURE.md#7-provisioner-terraform):
22
+
23
+ > `provisioner` is currently force-set to `'terraform'` in
24
+ > `Config::Base#add_static_options!`, so this dispatch is effectively
25
+ > fixed today.
26
+
27
+ `lib/cem_acpt/provision.rb` (factory) reads the key but never sees
28
+ anything other than `'terraform'` because of the static override.
29
+
30
+ ## Problem
31
+
32
+ - The factory pattern signals an extensibility surface that does not
33
+ exist.
34
+ - A user who sets `provisioner: foo` in config gets silently
35
+ overridden — `-X` will show the override but the runtime behavior
36
+ is unchanged.
37
+ - This blocks experimentation with a Pulumi/Packer/local-only
38
+ provisioner without first untangling the static set.
39
+
40
+ ## Proposal
41
+
42
+ Make the static layer set a *default* if absent, instead of
43
+ clobbering:
44
+
45
+ ```ruby
46
+ # Config::Base#add_static_options!
47
+ @config[:provisioner] ||= 'terraform'
48
+ ```
49
+
50
+ …and surface the relevant CLI flag in `Cli` (`--provisioner NAME`) so
51
+ users can opt into alternates. The `Provision.new_provisioner` factory
52
+ already raises a clear error for unknown values, which is the desired
53
+ behavior.
54
+
55
+ A complementary, even smaller change: if we don't actually intend to
56
+ support alternate provisioners, **remove the factory** and have
57
+ `TestRunner` instantiate `Provision::Terraform` directly. That is more
58
+ honest given today's code.
59
+
60
+ ## Recommendation
61
+
62
+ Do the smaller change (remove the factory and the dispatch) for now.
63
+ Re-introduce the factory when there is a concrete second
64
+ implementation in flight. Premature extensibility cost us little here,
65
+ but it adds a layer of indirection that contributors have to chase.
66
+
67
+ ## Alternatives Considered
68
+
69
+ - **Leave as-is.** Documentation drift continues; future contributors
70
+ keep mistaking the factory for an extension point.
71
+ - **Implement a second provisioner now.** Out of scope.
72
+
73
+ ## Risks & Migration
74
+
75
+ - No user-visible change if the factory is removed; the only code path
76
+ through it today is `'terraform'`.
77
+ - Any out-of-tree consumer that referenced `Provision.new_provisioner`
78
+ directly (none in this repo) would need to update.
79
+
80
+ ## Acceptance Criteria
81
+
82
+ - [ ] Decision recorded (factory kept-and-honored vs. factory
83
+ removed).
84
+ - [ ] Static-set in `Config::Base#add_static_options!` updated
85
+ accordingly.
86
+ - [ ] [`docs/ARCHITECTURE.md` §7](../ARCHITECTURE.md#7-provisioner-terraform)
87
+ and §19 updated.
88
+ - [ ] Spec coverage adjusted (the existing `terraform_cmd_spec.rb` is
89
+ unaffected).
@@ -0,0 +1,34 @@
1
+ # cem_acpt RFCs
2
+
3
+ Design proposals and bug-fix plans for `cem_acpt`. Each RFC is a
4
+ self-contained Markdown file using the layout in
5
+ [`0000-template.md`](0000-template.md).
6
+
7
+ ## Index
8
+
9
+ | # | Title | Priority | Category |
10
+ |------|------------------------------------------------------------------------------------|----------|-----------|
11
+ | 0001 | [Fix the bolt-missing skip path](0001-fix-bolt-missing-skip-path.md) | Critical | Bug |
12
+ | 0002 | [Fix malformed default `character_substitutions`](0002-fix-default-character-substitutions.md) | Critical | Bug |
13
+ | 0003 | [Ship a Windows image-builder Terraform template](0003-windows-image-builder-template.md) | Critical | Bug |
14
+ | 0004 | [Fix image-name truncation off-by-one](0004-image-name-truncation-off-by-one.md) | High | Bug |
15
+ | 0005 | [Replace `tests.first.include?('windows')` heuristic](0005-os-dispatch-replace-windows-heuristic.md) | High | Refactor |
16
+ | 0006 | [Make the Windows GCS bucket configurable](0006-configurable-windows-bucket.md) | High | Bug |
17
+ | 0007 | [Tighten `--quiet` semantics and fix logging typos](0007-logging-quiet-and-typos.md) | High | Bug |
18
+ | 0008 | [Namespace dynamically-created platform classes](0008-namespace-platform-classes.md) | Medium | Refactor |
19
+ | 0009 | [Wire up or remove orphaned Bolt log formatters](0009-bolt-log-formatter-cleanup.md) | Medium | Cleanup |
20
+ | 0010 | [Remove unreferenced top-level helpers and Windows placeholder](0010-dead-code-cleanup.md) | Medium | Cleanup |
21
+ | 0011 | [Make the provisioner factory honor configuration](0011-provisioner-factory-consistency.md) | Low | Refactor |
22
+
23
+ ## Status
24
+
25
+ All RFCs above are **Proposed** unless otherwise marked in the
26
+ individual files. Authorship and review notes belong inside each RFC.
27
+
28
+ ## Adding a new RFC
29
+
30
+ 1. Copy [`0000-template.md`](0000-template.md) to the next available
31
+ number.
32
+ 2. Fill in every section. Keep cross-references to
33
+ [`docs/ARCHITECTURE.md`](../ARCHITECTURE.md) accurate.
34
+ 3. Append a row to the index above.
data/lib/cem_acpt/cli.rb CHANGED
@@ -59,6 +59,12 @@ module CemAcpt
59
59
  options[:bolt] ||= {}
60
60
  options[:bolt][:keep_project] = true
61
61
  end
62
+
63
+ opts.on('--windows-bucket BUCKET', 'GCS bucket name for the Windows test path. Example: --windows-bucket "my-win-bucket"') do |b|
64
+ options[:platform] ||= {}
65
+ options[:platform][:gcp] ||= {}
66
+ options[:platform][:gcp][:windows_bucket] = b
67
+ end
62
68
  when :cem_acpt_image
63
69
  opts.on('--dry-run', 'Logs the information for the images that would be created. Does not create images.') do
64
70
  options[:dry_run] = true
@@ -126,7 +132,10 @@ module CemAcpt
126
132
  options[:no_destroy_nodes] = true
127
133
  end
128
134
 
129
- opts.on('-q', '--quiet', 'Do not log to stdout') do
135
+ opts.on(
136
+ '-q', '--quiet',
137
+ 'Drop stdout (requires --log-file outside CI; CI mode keeps stdout for ::group:: support)',
138
+ ) do
130
139
  options[:quiet] = true
131
140
  end
132
141
 
@@ -28,7 +28,7 @@ module CemAcpt
28
28
  ci_mode: false,
29
29
  config_file: nil,
30
30
  image_name_builder: {
31
- character_substitutions: ['_', '-'],
31
+ character_substitutions: [['_', '-']],
32
32
  parts: ['cem-acpt', '$image_fam', '$collection', '$firewall'],
33
33
  join_with: '-',
34
34
  },
@@ -40,6 +40,9 @@ module CemAcpt
40
40
  no_ephemeral_ssh_key: false,
41
41
  platform: {
42
42
  name: 'gcp',
43
+ gcp: {
44
+ windows_bucket: 'win_cem_acpt',
45
+ },
43
46
  },
44
47
  quiet: false,
45
48
  test_data: {