Skip to content

M6 — Filtering, validation, reporting, preview & publish gate - #116

Merged
loevgaard merged 4 commits into
1.xfrom
feature/m6-filter-validate-publish
Jul 2, 2026
Merged

M6 — Filtering, validation, reporting, preview & publish gate#116
loevgaard merged 4 commits into
1.xfrom
feature/m6-filter-validate-publish

Conversation

@loevgaard

Copy link
Copy Markdown
Member

M6 — Filtering, validation, reporting, preview, publish gate (§11, §6.6)

Four chunks; 581 tests; the Preview admin screen is browser-verified.

Filtering

  • FilterEvaluator resolves a FeedFilter's field/value via the reference rule + the shared
    operator vocabulary; exclude drops on match, include drops on non-match. Wired into the
    generator: pre filters run before mapping, post filters on the mapped output. Excluded
    items are counted and reported. SourceResolverBinder binds the item's source resolver once
    (before pre-filters). exclude-out-of-stock works.

Validation + reporting

  • FeedType::createItem() lets a type provide a typed FeedItem (product_variant
    GoogleShoppingItem). Symfony constraints on GoogleShoppingItem (required g: fields, title
    ≤150), applied format-scoped (RSS runs them; CSV/others fall back to the required-field list).
  • FeedContextResult resource records per-context itemCount/excludedCount/bytes + every exclusion
    with its reason (filter:{stage}:{field} / validation:{msg} / skipped), recorded at finalize.

Publish gate + guardrails

  • Feed.publishConfig guardrails (min_items, max_drop_pct, non_empty, min_bytes,
    max_exclusion_pct, max_growth_pct) compare the candidate vs the last-good FeedContextResult
    baseline; PublishGate blocks on a tripped block-severity guardrail, warns otherwise.
  • Finalize is now per-context + gated: GenerateFeedContextHandler runs the gate after
    recording (stamps publishState, dispatches FeedPublishBlockedEvent); MoveGeneratedFeedSubscriber
    no longer wipes canonical — it promotes only published contexts and retains blocked
    candidates in staging. A publish-anyway admin action promotes a retained candidate.
  • Acceptance: a 5k-vs-50k regenerate trips max_drop_pct → blocked, the live file is retained,
    the event fires.

Preview + audit

  • Shared MappingResolver (FeedField-first, preset fallback) reused by the generator and preview.
  • PreviewService runs the exact per-item pipeline over ≤N sampled items, writing nothing, returning
    a funnel (source → after-pre → after-mapping/validation → after-post → included), included
    sample bags, excluded samples with reasons, and a single-item tester.
  • FeedAuditService reports per-field fill rates, soft warnings (title>150, HTML-in-description,
    price=0) and value distributions — advisory only.
  • Admin Preview screen (/feeds/{id}/preview + grid action) and console --preview[=N] / --audit.

CI note

Verified locally against the updated PHPStan/Rector/ECS and PHP-8.1-pinned PHPStan + league/csv
9.6, so the gate (Unit / Integration / Static Code Analysis / Coding Standards) should be green. BC,
Dependency Analysis and Mutation remain the standing rewrite reds.

loevgaard added 4 commits July 2, 2026 15:49
- FilterEvaluator resolves a filter's field/value via the reference-resolution
  rule + the shared operator vocabulary; exclude drops on match, include drops on
  non-match. Wired into the generator: pre filters run before mapping, post
  filters on the mapped output; excluded items are counted and skipped.
- SourceResolverBinder: bind the item's on-demand source resolver once (before
  pre-filters, reused by mapping) preserving lookup: delegation; idempotent so
  FieldMappingEvaluator is unchanged.
- FeedFilterType admin form + a filters collection on FeedSourceType.
- exclude-out-of-stock proven end to end.

+9 tests (512 total); PHPStan max (incl. PHP 8.1) + ECS + Rector + container green.
- FeedType::createItem() lets a feed type provide a typed FeedItem (product_variant
  -> GoogleShoppingItem). Symfony constraints on GoogleShoppingItem (required g:
  fields + title<=150), applied format-scoped (FormatInterface::getItemValidationGroups;
  google_rss runs them, CSV/others fall back to the required-field list) so a CSV
  product_variant feed isn't held to the RSS spec.
- FeedItemValidator: typed constraint violations + required-field fallback, deduped.
- FeedContextResult resource (feed, contextKey, itemCount/excludedCount/bytes,
  errors json, createdAt) + repository (findLatestPublished) — the publish-gate
  baseline. Every exclusion is recorded with a reason (filter:{stage}:{field} /
  validation:{msg} / skipped), capped, via ExclusionCollector; recorded at finalize
  by FeedContextResultRecorder.

+11 tests (523 total); PHPStan max (incl. 8.1) + ECS + Rector + container green.
- Feed.publishConfig (json guardrails); GuardrailRegistry + guardrails
  (min_items, max_drop_pct, non_empty, min_bytes, max_exclusion_pct,
  max_growth_pct) comparing candidate vs the last-good FeedContextResult baseline.
- PublishGate returns a decision (blocked + reasons); block-severity trips block,
  warn only records; empty guardrails = no gate.
- FeedContextResult gains publishState (pending/published/blocked) + publishCheck;
  findLatestPublished filters published so a candidate is never its own baseline.
- Finalize is now per-context + gated: GenerateFeedContextHandler runs the gate
  after recording, stamps publishState, dispatches FeedPublishBlockedEvent when
  blocked/warned; MoveGeneratedFeedSubscriber no longer wipes canonical -- it
  promotes only published contexts and retains blocked candidates in staging.
- Publish-anyway admin action promotes a retained blocked candidate.

Acceptance: a 5k-vs-50k regenerate trips max_drop_pct -> blocked, live file
retained, event fired. +44 tests (567 total); all gates (incl. PHP 8.1) green.
- Extract MappingResolver (FeedField-first, preset fallback) shared by the
  generator + preview so the pipeline is not forked.
- PreviewService runs the exact per-item pipeline (bind -> pre-filter -> map ->
  post-filter -> validate) over <=N sampled items, writing nothing, and returns a
  funnel (source -> after pre -> after mapping/validation -> after post ->
  included), included sample bags, excluded samples with the dropping reason, and
  a single-item tester (previewItem by id).
- FeedAuditService computes per-field fill rates, soft warnings (title>150,
  HTML-in-description, price=0) and value distributions over the included sample
  -- advisory, excludes nothing.
- Admin Preview screen (/feeds/{id}/preview + grid action) and console
  --preview[=N] / --audit options.

+14 tests (581 total); all gates (incl. PHP 8.1 + twig lint) green.
@loevgaard
loevgaard merged commit ab10d4f into 1.x Jul 2, 2026
20 of 28 checks passed
@loevgaard
loevgaard deleted the feature/m6-filter-validate-publish branch July 2, 2026 15:23
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.24517% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.25%. Comparing base (f76b70b) to head (e9326c3).
⚠️ Report is 5 commits behind head on 1.x.

Files with missing lines Patch % Lines
src/Preview/PreviewService.php 90.38% 10 Missing ⚠️
src/Audit/FeedAuditService.php 91.11% 8 Missing ⚠️
src/Command/ProcessFeedCommand.php 91.17% 6 Missing ⚠️
src/Publish/PublishGate.php 89.65% 3 Missing ⚠️
src/Format/GenericXmlFormat.php 0.00% 2 Missing ⚠️
src/Generator/FeedGenerator.php 93.54% 2 Missing ⚠️
src/Generator/SourceResolverBinder.php 92.30% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##                1.x     #116      +/-   ##
============================================
- Coverage     95.32%   95.25%   -0.07%     
- Complexity      998     1231     +233     
============================================
  Files           134      161      +27     
  Lines          2417     3057     +640     
============================================
+ Hits           2304     2912     +608     
- Misses          113      145      +32     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant