diff --git a/core/test/lib/workarea/error_reporting_test.rb b/core/test/lib/workarea/error_reporting_test.rb new file mode 100644 index 000000000..3379e00fc --- /dev/null +++ b/core/test/lib/workarea/error_reporting_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +module Workarea + class ErrorReportingTest < TestCase + class ReporterDouble + attr_reader :calls + + def initialize(raises: false) + @raises = raises + @calls = [] + end + + def report(error, handled:, severity:, context:) + raise StandardError, 'reporter failure' if @raises + + @calls << { + error: error, + handled: handled, + severity: severity, + context: context + } + + :reported + end + end + + def test_report_forwards_to_rails_error_when_available + error = StandardError.new('boom') + reporter = ReporterDouble.new + + Rails.stub(:error, reporter) do + result = Workarea::ErrorReporting.report( + error, + handled: true, + severity: :warning, + context: { service: 'rubygems.org' } + ) + + assert_equal(:reported, result) + assert_equal(1, reporter.calls.length) + assert_equal(error, reporter.calls.first[:error]) + assert_equal(true, reporter.calls.first[:handled]) + assert_equal(:warning, reporter.calls.first[:severity]) + assert_equal({ service: 'rubygems.org' }, reporter.calls.first[:context]) + end + end + + def test_report_returns_nil_when_rails_error_is_unavailable + error = StandardError.new('boom') + + Rails.stub(:error, nil) do + assert_nil(Workarea::ErrorReporting.report(error)) + end + end + + def test_report_swallows_reporter_failures + error = StandardError.new('boom') + reporter = ReporterDouble.new(raises: true) + + Rails.stub(:error, reporter) do + assert_nil(Workarea::ErrorReporting.report(error)) + end + end + end +end diff --git a/docs/rails7-migration-patterns/error-reporting.md b/docs/rails7-migration-patterns/error-reporting.md index 566259234..754ad4bb8 100644 --- a/docs/rails7-migration-patterns/error-reporting.md +++ b/docs/rails7-migration-patterns/error-reporting.md @@ -8,9 +8,9 @@ This is implemented by `ActiveSupport::ErrorReporter` and is intended to be **configured by the host application** (or an integration gem) to forward handled exceptions to a provider (Sentry, Bugsnag, Honeybadger, etc.). -## Verification status (WA-VERIFY-044) +## Verification status (WA-VERIFY-114) -**Status: ✅ Complete — implemented and compatible.** +**Status: ✅ Complete — audited and compatible.** Workarea implements `Workarea::ErrorReporting` as a thin wrapper around `Rails.error.report`. The wrapper is availability-guarded so it degrades @@ -23,6 +23,23 @@ Workarea **does not ship with a bundled error reporting provider**. Instead, Workarea relies on the host application to configure an error reporting solution at the Rack / Rails level. +## Audited integration points + +The audit found a small, additive integration surface only: + +- `core/lib/workarea/error_reporting.rb` provides `Workarea::ErrorReporting`, a + compatibility wrapper around `Rails.error.report`. +- `core/lib/workarea/latest_version.rb`, + `core/lib/workarea/ping_home_base.rb`, and + `core/app/models/workarea/checkout/fraud/analyzer.rb` use that wrapper for + handled exceptions that Workarea intentionally rescues. +- `storefront/app/controllers/workarea/storefront/errors_controller.rb` sets + `request.env['rack.exception']` for 500 responses so Rack-level reporters can + observe unhandled exceptions through the normal middleware path. + +No additional `ActiveSupport::ErrorReporter` subscribers, custom Rails error +reporter configuration, or provider-specific integrations are present in core. + Examples: - Storefront error pages set `request.env['rack.exception']` in @@ -51,15 +68,23 @@ Host applications can configure Rails' error reporter via `config.error_reporter ## Decision -**Adopted Rails 7.1's error reporting API as an additive, opt-in hook:** +**Rails 7.1's error reporting APIs do not introduce a compatibility break for +Workarea internals or extension points.** - Workarea calls `Rails.error.report` **only when available**. - Workarea does not require any provider. -- Existing error handling continues to work unchanged. +- Existing error handling and Rack-based reporting continue to work unchanged. +- Extension points remain stable because host applications and plugins can opt + into `config.error_reporter` without needing Workarea-specific changes. This is useful primarily for *handled/swallowed* exceptions where otherwise the host app may never learn about the error. +## Client impact + +**None expected.** Existing applications, plugins, and downstream integrations do +not need code changes to remain compatible. + ## Verification commands ```bash diff --git a/docs/research/actionview-template-handlers-rails7-1-compat.md b/docs/research/actionview-template-handlers-rails7-1-compat.md new file mode 100644 index 000000000..70479223c --- /dev/null +++ b/docs/research/actionview-template-handlers-rails7-1-compat.md @@ -0,0 +1,120 @@ +# ActionView Template Handler Rails 7.1 Compatibility + +**Issue:** WA-VERIFY-113 / workarea-commerce/workarea#1137 +**Branch:** `wa-verify-113-template-handler-compat` +**Date:** 2026-03-21 +**Conclusion:** No Workarea-side template handler breakage found for Rails 7.1. + +## Summary + +This audit checked Workarea for: + +- custom `ActionView` template handler registrations +- nonstandard view rendering hooks that could depend on handler internals +- existing template engines used by the app (`haml`, `jbuilder`, `builder`) + +Result: **Workarea does not register custom ActionView template handlers**, and the +repo's view-layer extensions stay at the normal partial/render API level. No +Workarea code changes are required for Rails 7.1 template handler compatibility. + +## What was searched + +Repo-wide targeted searches covered `core/`, `admin/`, and `storefront/` for: + +- `ActionView::Template.register_template_handler` +- `ActionView::Template::Handlers` +- `register_renderer` +- `render_to_body` +- `lookup_context` +- `view_paths` +- `render inline:` +- direct `JbuilderTemplate` extensions +- template engine usage (`.haml`, `.jbuilder`, `.builder`) + +## Findings + +### 1) No custom template handler registrations in Workarea + +Searches found **no** uses of: + +- `ActionView::Template.register_template_handler` +- `ActionView::Template::Handlers` +- custom handler classes/modules + +That means Workarea is not depending on deprecated handler registration APIs or +single-arity handler implementations in its own code. + +### 2) HAML remains the primary template engine + +Current engine usage in the repo: + +- `793` `.haml` templates +- `13` `.jbuilder` templates +- `1` `.builder` template + +Workarea already documents HAML compatibility in +`docs/research/haml-rails7-compat.md`. +That research confirms `haml 5.2.2` exposes a Rails-compatible two-argument +handler entrypoint: + +```ruby +Haml::Plugin.call(template, source = nil) +``` + +That is the relevant Rails 7+ contract for template handlers. + +### 3) Jbuilder usage extends rendering behavior, not handler registration + +Workarea has two Jbuilder extensions: + +- `core/lib/workarea/ext/jbuilder/jbuilder_append_partials.rb` +- `core/lib/workarea/ext/jbuilder/jbuilder_cache.rb` + +These modify `JbuilderTemplate` behavior via `prepend` / decoration, but they do +**not** register a custom template handler or bypass normal ActionView rendering. +They continue to operate through standard `@context.render(...)` and cache hooks. + +### 4) Nonstandard rendering hooks are limited and Rails 7.1-safe on inspection + +The only notable view lookup customizations found were standard `lookup_context` +usage, such as: + +- checking for optional partials before rendering +- verifying style guide template existence + +These calls use normal ActionView lookup APIs and do not depend on template +handler internals. + +## Rails 7.1 appraisal status + +The repo contains `gemfiles/rails_7_1.gemfile`, pinned to `rails 7.1.5.1`, but +it currently includes this note: + +> As of 2026-03-17, this appraisal does not resolve due to mongoid (< 8.0.7) +> constraining activemodel to < 7.1, while Rails 7.1 pins activemodel 7.1.x. + +So a live Rails 7.1 smoke test for this area is presently blocked by dependency +resolution unrelated to ActionView template handlers. + +## Compatibility result + +**PASS** — No Workarea-owned template handler compatibility gap was found for +Rails 7.1. + +### Verified safe + +- No custom ActionView template handler registrations in Workarea +- No Workarea-owned single-arity template handlers +- HAML compatibility already documented and aligned with Rails 7 handler arity +- Jbuilder customizations stay within public rendering/template APIs +- `lookup_context` usage is standard and not handler-internal + +### Known limitation + +- Rails 7.1 runtime smoke-testing is currently blocked by the existing + `mongoid` / `activemodel` appraisal resolution issue, so this verification is + based on code audit plus existing HAML compatibility research + +## Client impact + +None expected. diff --git a/docs/verification/wa-verify-111-zeitwerk-edge-cases.md b/docs/verification/wa-verify-111-zeitwerk-edge-cases.md new file mode 100644 index 000000000..03dec376e --- /dev/null +++ b/docs/verification/wa-verify-111-zeitwerk-edge-cases.md @@ -0,0 +1,169 @@ +# WA-VERIFY-111 — Zeitwerk Edge Cases Across Decorators, Overrides, and Extension Points + +## Summary + +Audited the Workarea extension patterns most commonly used by downstream applications and plugins for Zeitwerk compatibility: + +- Ruby decorators (`app/**/*.decorator`) +- test decorators (`test/**/*.decorator`) +- front-end overrides generated under app/view/layout/asset paths +- plugin extension hooks that load files outside normal autoload paths +- mailer preview loading + +## Result + +**Status: ✅ Compatible for the audited downstream extension patterns.** + +No downstream client code changes are expected. + +The audit did identify one **existing core-side autoload workaround** in `Workarea::Core::Engine`: two explicit `require` calls in `config.to_prepare` for constants that do not reliably autoload during boot/runtime in current dummy-app environments. + +That workaround is already scoped to core boot behavior and does **not** change the documented downstream decorator/override patterns. + +## Catalog of audited patterns + +### 1. Ruby decorators + +Relevant files: + +- `core/lib/generators/workarea/decorator/decorator_generator.rb` +- `core/lib/generators/workarea/decorator/templates/decorator.rb.erb` + +Observed pattern: + +- Decorators are generated as `app/**/*.decorator`, not as Zeitwerk-managed `*.rb` constants. +- The generator resolves an existing source file first, then creates a parallel `.decorator` path. +- The decorator template uses `decorate , with: :name do ... end`, which is an explicit decoration hook rather than implicit autoload discovery. + +Zeitwerk assessment: + +- **Compatible.** These files are not relying on Zeitwerk constant-to-file inference. +- Decoration is an explicit extension mechanism, so the `.decorator` suffix is not itself a Zeitwerk problem. + +### 2. Test decorators + +Relevant files: + +- `testing/lib/workarea/test_case.rb` +- `core/lib/tasks/tests.rake` + +Observed pattern: + +- `Workarea::TestCase` derives the current test file path and looks for a same-path `test/**/*.decorator` file across installed plugins and the host app. +- Decorator files are loaded explicitly with `load` and tracked in `loaded_decorators` to avoid duplicate loads. +- The decorated test rake task enforces path parity between the original test and its decorator. + +Zeitwerk assessment: + +- **Compatible.** This path is explicit file loading, not autoloading. +- The same-path requirement avoids ambiguous constant/file resolution. + +### 3. View/layout/asset overrides + +Relevant files: + +- `core/lib/generators/workarea/override/USAGE` + +Observed pattern: + +- Overrides are generated as application/plugin-owned copies of views, layouts, stylesheets, javascripts, images, fonts, etc. +- These are resolved by Rails view lookup / asset lookup, not by Zeitwerk constant loading. + +Zeitwerk assessment: + +- **Compatible.** These override patterns do not depend on Zeitwerk naming conventions. + +### 4. Mailer previews and other file-based extension points + +Relevant files: + +- `core/config/initializers/19_mailer_previews.rb` + +Observed pattern: + +- Preview files are loaded from plugin roots and preview paths using `load` inside `config.to_prepare`. +- This is intentionally reload-friendly and independent from Zeitwerk autoloading. + +Zeitwerk assessment: + +- **Compatible.** The pattern is explicit and reload-aware. + +### 5. Engine-managed autoload extensions + +Relevant files: + +- `core/lib/workarea/core/engine.rb` + +Observed pattern: + +- Core adds non-standard app directories to `config.autoload_paths`: + - `app/queries` + - `app/seeds` + - `app/services` + - `app/view_models` + - `app/workers` +- These paths remain conventional from a Zeitwerk perspective when file names and constants match. + +Zeitwerk assessment: + +- **Compatible, with one notable exception below.** + +## Notable core-side exception + +`core/lib/workarea/core/engine.rb` currently includes: + +```ruby +config.to_prepare do + require 'workarea/bulk_index_products' + require 'workarea/metrics/user' +end +``` + +The surrounding comments already document that these are needed because the constants do not reliably autoload in current runtime paths. + +Assessment: + +- This is **not a downstream decorator/override incompatibility**. +- It is an **existing core autoload edge case** that is already handled safely with explicit requires. +- No additional change was made here because removing the workaround would be speculative and could re-introduce runtime `NameError`s. + +## Verification performed + +### Repository audit + +Searched the core engines and shared test support for: + +- `.decorator` usage +- `config.to_prepare` +- `require_dependency` +- custom autoload/eager-load path additions +- preview loading and other explicit file-loading patterns + +### Targeted command check + +Attempted: + +- `./scripts/verify-zeitwerk.sh` + +Result: + +- The script could not complete in this environment because Bundler rejected `workarea.gemspec` under the available Ruby/Bundler combination before Rails booted. +- Failure was environmental/tooling-related, not an application autoload failure: + - `Illformed requirement [">= 2.7.0, < 3.5.0"]` + +Because of that environment constraint, this verification was completed through targeted source audit rather than a fresh successful runtime Zeitwerk pass in this session. + +## Conclusion + +For the extension patterns downstream Workarea implementations commonly use: + +- decorators are explicitly loaded and remain Zeitwerk-safe +- test decorators are explicitly loaded and path-validated +- front-end overrides are lookup-based, not autoload-based +- mailer preview loading is explicit and reload-safe + +The only notable edge case found in the audited area is an **already-contained core workaround** for two constants that are explicitly required during `to_prepare`. + +## Client impact + +**None expected.** diff --git a/docs/verification/wa-verify-112-active-record-attribute-api.md b/docs/verification/wa-verify-112-active-record-attribute-api.md new file mode 100644 index 000000000..195087aba --- /dev/null +++ b/docs/verification/wa-verify-112-active-record-attribute-api.md @@ -0,0 +1,110 @@ +# WA-VERIFY-112 — Verify ActiveRecord attribute API assumptions in Rails 7.1 migration paths + +Closes #1136 + +## Scope + +This audit looked for places where Workarea might accidentally assume **ActiveRecord attribute internals** while operating across mixed persistence boundaries (primarily Mongoid documents, serialized objects, and integration/view-model rebuild paths). + +The concern for Rails 7.1 is whether code paths rely on ActiveRecord-specific attribute internals or pre-7.1 behavior that would break once downstream apps upgrade Rails while still running Workarea’s Mongoid-heavy core. + +## Search summary + +Repository searches covered: + +- `read_attribute` / `write_attribute` +- `attribute_before_type_cast` / `attributes_before_type_cast` +- `attribute_types` / `type_for_attribute` / `column_for_attribute` +- direct `attributes[...]` mutation +- `serializable_hash` / `as_json` / `as_document` +- `instantiate(...)` / `Mongoid::Factory.from_db(...)` +- docs references to Rails 7.1 and ActiveRecord behavior + +## Relevant touchpoints reviewed + +### 1) Mongoid-backed model attribute access + +These uses remain within Mongoid/ActiveModel-supported APIs and do **not** depend on ActiveRecord’s internal attribute objects: + +- `core/app/models/workarea/content.rb` + - `read_attribute(:name)` +- `core/app/models/workarea/inventory/capture.rb` + - `read_attribute(:sellable)` +- `core/app/models/workarea/search/customization.rb` + - `read_attribute(:redirect)` +- `core/app/models/workarea/data_file/import.rb` + - `read_attribute(:file_type)` + +### 2) Release changeset replay / dirty tracking + +`core/app/models/workarea/release/changeset.rb` replays persisted changes with: + +- `model.send(:attribute_will_change!, field)` +- `model.attributes[field] = ...` +- `releasable_from_document_path.attributes[key]` + +This path operates on **Mongoid documents**, not ActiveRecord models. The API surface used here is still available through ActiveModel dirty tracking and Mongoid attribute hashes. No Rails 7.1-specific ActiveRecord attribute object assumptions were found. + +### 3) Serialization / rehydration boundaries + +The mixed persistence/integration boundaries that were most likely to expose AR-attribute assumptions already use document/hash serialization rather than ActiveRecord internals: + +- `core/lib/workarea/elasticsearch/serializer.rb` + - Mongoid models serialize via `as_document` + - deserialize via `klass.instantiate(...)` + - explicitly avoids `Mongoid::Factory.from_db` +- `storefront/app/view_models/workarea/storefront/order_item_view_model.rb` +- `admin/app/view_models/workarea/admin/order_item_view_model.rb` + - use `Mongoid::Factory.from_db(...)` with document hashes +- `core/app/models/workarea/content/block.rb` +- `core/app/models/workarea/content/block_draft.rb` +- `core/app/models/workarea/pricing/request.rb` + - clone/save flows operate on `as_document` payloads and plain hashes + +These paths do not reach into `ActiveRecord::AttributeSet`, `attribute_types`, `attributes_before_type_cast`, or similar Rails 7.1-sensitive APIs. + +### 4) Docs / migration guidance + +No Rails 7.1 migration document in this repo currently instructs downstream apps to depend on ActiveRecord attribute internals for Workarea-managed code paths. + +## Findings + +### Result: **no Rails 7.1 ActiveRecord attribute API incompatibility found** + +I did **not** find any Workarea core/admin/storefront code path that: + +- assumes ActiveRecord’s internal attribute container structure, +- depends on pre-7.1 ActiveRecord dirty-tracking internals, +- mixes Mongoid documents with ActiveRecord-only attribute APIs at runtime, or +- requires downstream clients to change attribute-access code as part of a Rails 7.1 upgrade. + +The reviewed touchpoints use either: + +- Mongoid’s document/hash APIs (`as_document`, `instantiate`, `attributes`), or +- ActiveModel-compatible attribute/dirty APIs (`read_attribute`, `attribute_will_change!`). + +Those are compatible with the Rails 7.1 migration concern being audited here. + +## Verification notes + +Targeted existing tests identified for the reviewed areas: + +- `core/test/models/workarea/content_test.rb` +- `core/test/models/workarea/release/changeset_test.rb` +- `core/test/elasticsearch/workarea/elasticsearch/serializer_test.rb` + +Attempting to run them in this checkout is currently blocked before test boot by an existing Bundler/gemspec parsing issue: + +- `workarea.gemspec`: `s.required_ruby_version = '>= 2.7.0, < 3.5.0'` + +On this machine/toolchain, Bundler fails while parsing that combined requirement string, so the verification for this issue is based on code audit plus existing test coverage review rather than a green local test execution. + +## Recommendation + +No code change is recommended for WA-VERIFY-112. + +If a future Rails upgrade introduces a real regression here, the highest-risk areas to re-check first are: + +1. `Release::Changeset` replay/dirtiness behavior, +2. serializer rehydration boundaries using `instantiate` / `from_db`, +3. clone/save flows in `Pricing::Request` that round-trip document hashes.