From 2a64905cc99a1f24719675e10211812d47419f44 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 21 Jun 2026 12:03:09 -0400 Subject: [PATCH] Harden php-transformer report consistency --- php-transformer/docs/contracts/result-envelope.md | 2 +- .../src/Contract/ConversionReportProjection.php | 9 +++++++-- php-transformer/tests/contract/run.php | 6 ++++++ .../parity/full-site-materialization-contract.json | 5 +++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/php-transformer/docs/contracts/result-envelope.md b/php-transformer/docs/contracts/result-envelope.md index 7cb4002..2a11763 100644 --- a/php-transformer/docs/contracts/result-envelope.md +++ b/php-transformer/docs/contracts/result-envelope.md @@ -40,7 +40,7 @@ Artifact compiler callers that write compiled output should read `source_reports The materialization plan also includes product-neutral `routes`, `navigation_links`, and `menus` rows derived from compiled pages and navigation markup. These rows use source and target fields such as `source_path`, `target_path`, `target_slug`, `title`, `label`, `parent_source_path`, `source_relation`, `order`, and `kind`; they do not assign WordPress post IDs, terms, menu locations, or persistence policy. Product adapters remain responsible for deciding whether and how to create pages, menus, navigation entities, route rewrites, and imported assets. -Transformers may expose `source_reports.conversion_report` as a generic `blocks-engine/php-transformer/conversion-report/v1` projection over the same result envelope. It summarizes fallback diagnostics, source paths/selectors, artifact-local asset references, navigation candidates, presentation-gap signals, and metrics without applying product rewrite, import, routing, or visual-parity policy. +Transformers may expose `source_reports.conversion_report` as a generic `blocks-engine/php-transformer/conversion-report/v1` projection over the same result envelope. It summarizes fallback diagnostics, source paths/selectors, artifact-local asset references, navigation candidates, presentation-gap signals, and metrics without applying product rewrite, import, routing, or visual-parity policy. For artifact compiles, `source_summary` mirrors canonical materialization counts such as `page_count`, `asset_count`, `route_count`, `navigation_link_count`, and `menu_count` so wrappers can validate report consistency without re-deriving write plans from product-specific assumptions. `components` contains inspectable component candidates discovered before materialization. `block_types` contains generated block type artifacts promoted from `block.json` roots. `legacy_mapping` is transitional metadata for consumer-owned migration mappers and is not a long-term package feature commitment. diff --git a/php-transformer/src/Contract/ConversionReportProjection.php b/php-transformer/src/Contract/ConversionReportProjection.php index 32e1201..75b8e9e 100644 --- a/php-transformer/src/Contract/ConversionReportProjection.php +++ b/php-transformer/src/Contract/ConversionReportProjection.php @@ -47,6 +47,8 @@ private static function sourceSummary(string $sourceFormat, array $blocks, array { $artifact = is_array($sourceReports['artifact'] ?? null) ? $sourceReports['artifact'] : array(); $compiledSite = is_array($sourceReports['compiled_site'] ?? null) ? $sourceReports['compiled_site'] : array(); + $materializationPlan = is_array($sourceReports['materialization_plan'] ?? null) ? $sourceReports['materialization_plan'] : array(); + $materializationTotals = is_array($materializationPlan['totals'] ?? null) ? $materializationPlan['totals'] : array(); return array_filter( array( @@ -55,8 +57,11 @@ private static function sourceSummary(string $sourceFormat, array $blocks, array 'file_count' => $artifact['file_count'] ?? null, 'accepted_count' => $artifact['accepted_count'] ?? null, 'rejected_count' => $artifact['rejected_count'] ?? null, - 'page_count' => isset($compiledSite['pages']) && is_array($compiledSite['pages']) ? count($compiledSite['pages']) : null, - 'asset_count' => 0 < count($assets) ? count($assets) : null, + 'page_count' => $materializationTotals['pages'] ?? (isset($compiledSite['pages']) && is_array($compiledSite['pages']) ? count($compiledSite['pages']) : null), + 'asset_count' => $materializationTotals['assets'] ?? (0 < count($assets) ? count($assets) : null), + 'route_count' => $materializationTotals['routes'] ?? null, + 'navigation_link_count' => $materializationTotals['navigation_links'] ?? null, + 'menu_count' => $materializationTotals['menus'] ?? null, 'block_count' => $metrics['block_count'] ?? self::countBlocks($blocks), 'fallback_count' => $metrics['fallback_count'] ?? count($fallbacks), 'diagnostic_count' => $metrics['diagnostic_count'] ?? null, diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php index 3b43848..0d10191 100644 --- a/php-transformer/tests/contract/run.php +++ b/php-transformer/tests/contract/run.php @@ -161,6 +161,12 @@ function serialize_blocks(array $blocks): string $assert('/about' === ($staticPlan['navigation_links'][1]['target_path'] ?? ''), 'materialization plan exposes navigation target paths'); $assert('menu' === ($staticPlan['menus'][0]['kind'] ?? ''), 'materialization plan exposes generic menu rows'); $assert(2 === ($staticPlan['menus'][0]['items'] ?? null), 'materialization plan counts menu items'); +$staticSummary = $staticSite['source_reports']['conversion_report']['source_summary'] ?? array(); +$assert(($staticPlan['totals']['pages'] ?? null) === ($staticSummary['page_count'] ?? null), 'conversion report page count matches materialization plan totals'); +$assert(($staticPlan['totals']['assets'] ?? null) === ($staticSummary['asset_count'] ?? null), 'conversion report asset count matches materialization plan totals'); +$assert(($staticPlan['totals']['routes'] ?? null) === ($staticSummary['route_count'] ?? null), 'conversion report route count matches materialization plan totals'); +$assert(($staticPlan['totals']['navigation_links'] ?? null) === ($staticSummary['navigation_link_count'] ?? null), 'conversion report navigation link count matches materialization plan totals'); +$assert(($staticPlan['totals']['menus'] ?? null) === ($staticSummary['menu_count'] ?? null), 'conversion report menu count matches materialization plan totals'); $logoAssetPlanRow = null; $cssAssetPlanRow = null; foreach ( $staticPlan['assets'] ?? array() as $assetPlanRow ) { diff --git a/php-transformer/tests/fixtures/parity/full-site-materialization-contract.json b/php-transformer/tests/fixtures/parity/full-site-materialization-contract.json index 9ad46b1..1cbaa62 100644 --- a/php-transformer/tests/fixtures/parity/full-site-materialization-contract.json +++ b/php-transformer/tests/fixtures/parity/full-site-materialization-contract.json @@ -147,6 +147,11 @@ { "path": "source_reports.conversion_report.schema", "assert": "equals", "value": "blocks-engine/php-transformer/conversion-report/v1" }, { "path": "source_reports.conversion_report.source_summary.entry_path", "assert": "equals", "value": "site/index.html" }, { "path": "source_reports.conversion_report.source_summary.file_count", "assert": "equals", "value": 10 }, + { "path": "source_reports.conversion_report.source_summary.page_count", "assert": "equals", "value": 3 }, + { "path": "source_reports.conversion_report.source_summary.asset_count", "assert": "equals", "value": 9 }, + { "path": "source_reports.conversion_report.source_summary.route_count", "assert": "equals", "value": 3 }, + { "path": "source_reports.conversion_report.source_summary.navigation_link_count", "assert": "equals", "value": 3 }, + { "path": "source_reports.conversion_report.source_summary.menu_count", "assert": "equals", "value": 1 }, { "path": "source_reports.conversion_report.asset_refs", "assert": "count", "count": 2 }, { "path": "source_reports.conversion_report.selector_summary.source_paths", "assert": "count", "count": 2 }, { "path": "source_reports.conversion_report.presentation_gaps.0.type", "assert": "equals", "value": "presentation_stylesheet" },