TenantAtlas/apps/platform/app/Services/TenantReviews/TenantReviewComposer.php
ahmido ce0615a9c1 Spec 182: relocate Laravel platform to apps/platform (#213)
## Summary
- move the Laravel application into `apps/platform` and keep the repository root for orchestration, docs, and tooling
- update the local command model, Sail/Docker wiring, runtime paths, and ignore rules around the new platform location
- add relocation quickstart/contracts plus focused smoke coverage for bootstrap, command model, routes, and runtime behavior

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PlatformRelocation`
- integrated browser smoke validated `/up`, `/`, `/admin`, `/admin/choose-workspace`, and tenant route semantics for `200`, `403`, and `404`

## Remaining Rollout Checks
- validate Dokploy build context and working-directory assumptions against the new `apps/platform` layout
- confirm web, queue, and scheduler processes all start from the expected working directory in staging/production
- verify no legacy volume mounts or asset-publish paths still point at the old root-level `public/` or `storage/` locations

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #213
2026-04-08 08:40:47 +00:00

72 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantReviews;
use App\Models\EvidenceSnapshot;
use App\Models\TenantReview;
use App\Support\TenantReviewStatus;
final class TenantReviewComposer
{
public function __construct(
private readonly TenantReviewFingerprint $fingerprint,
private readonly TenantReviewSectionFactory $sectionFactory,
private readonly TenantReviewReadinessGate $readinessGate,
) {}
/**
* @return array{
* fingerprint: string,
* completeness_state: string,
* status: string,
* summary: array<string, mixed>,
* sections: list<array<string, mixed>>
* }
*/
public function compose(EvidenceSnapshot $snapshot, ?TenantReview $review = null): array
{
$tenant = $snapshot->tenant;
if ($tenant === null) {
throw new \RuntimeException('Evidence snapshot tenant is required for review composition.');
}
$sections = $this->sectionFactory->make($snapshot);
$blockers = $this->readinessGate->blockersForSections($sections);
$sectionStateCounts = $this->readinessGate->sectionStateCounts($sections);
$completeness = $this->readinessGate->completenessForSections($sections);
$status = $this->readinessGate->statusForSections($sections);
if ($review instanceof TenantReview && $review->isPublished()) {
$status = TenantReviewStatus::Published;
}
return [
'fingerprint' => $this->fingerprint->forSnapshot($tenant, $snapshot),
'completeness_state' => $completeness->value,
'status' => $status->value,
'summary' => [
'evidence_basis' => [
'snapshot_id' => (int) $snapshot->getKey(),
'snapshot_fingerprint' => (string) $snapshot->fingerprint,
'snapshot_completeness_state' => (string) $snapshot->completeness_state,
'snapshot_generated_at' => $snapshot->generated_at?->toIso8601String(),
],
'section_count' => count($sections),
'section_state_counts' => $sectionStateCounts,
'publish_blockers' => $blockers,
'has_ready_export' => false,
'finding_count' => (int) data_get($sections, '0.summary_payload.finding_count', 0),
'report_count' => 2,
'operation_count' => (int) data_get($sections, '5.summary_payload.operation_count', 0),
'highlights' => data_get($sections, '0.render_payload.highlights', []),
'recommended_next_actions' => data_get($sections, '0.render_payload.next_actions', []),
'last_composed_at' => now()->toIso8601String(),
],
'sections' => $sections,
];
}
}