# Implementation Plan: Canonical Control Catalog Foundation **Branch**: `236-canonical-control-catalog-foundation` | **Date**: 2026-04-24 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/236-canonical-control-catalog-foundation/spec.md` **Note**: This plan keeps the slice intentionally narrow. It introduces one product-seeded canonical control catalog plus one shared resolver contract, then adopts that contract only in findings-derived evidence composition and tenant review composition without adding operator CRUD, Graph sync, or a new operator-facing surface. ## Summary Add a config-seeded canonical control catalog in `apps/platform/config/canonical_controls.php` plus a small `App\Support\Governance\Controls` value-object and resolver layer so the same governance objective resolves to one stable control identity, one honest detectability story, and one provider-neutral vocabulary. The implementation will keep Microsoft workload, subject-family, and signal mappings as secondary provider-owned bindings, expose explicit `resolved`, `unresolved`, and `ambiguous` outcomes, and adopt the shared contract only in the existing findings-derived evidence composition and tenant review composition paths instead of widening into baseline, exception, report, or review-pack consumers in this slice. ## Technical Context **Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 **Primary Dependencies**: existing governance support types under `App\Support\Governance`, `EvidenceSnapshotResolver`, `EvidenceSnapshotService`, `FindingsSummarySource`, `TenantReviewComposer`, `TenantReviewSectionFactory`, `TenantReviewService`, Pest v4 **Storage**: Existing PostgreSQL tables for downstream evidence and tenant review records; product-seeded in-repo config for canonical control definitions and Microsoft bindings **Testing**: Pest v4 unit and feature tests through Laravel Sail **Validation Lanes**: `fast-feedback`, `confidence` **Target Platform**: Laravel admin web application running in Sail with existing `/admin` and `/admin/t/{tenant}` surfaces **Project Type**: Monorepo with one Laravel runtime in `apps/platform` and spec artifacts at repository root **Performance Goals**: Keep catalog lookup deterministic and in-process, add no outbound provider calls, and avoid new high-cardinality or repeated per-item resolver work in evidence or tenant review composition **Constraints**: No new Graph calls, no sync job, no DB-backed control authoring UI, no new operator-facing page, no new persistence table, and no provider-specific vocabulary leaking into platform-core control identity **Scale/Scope**: One config-backed catalog, one shared resolver, one bounded Microsoft binding family, two first-slice downstream adoption paths, and focused governance foundation unit plus feature tests ## Filament v5 Implementation Contract - **Livewire v4.0+ compliance**: Preserved. This slice changes shared services and value objects only and introduces no legacy Livewire patterns. - **Provider registration location**: Unchanged. Panel providers remain registered in `apps/platform/bootstrap/providers.php`. - **Global search coverage**: No new Filament Resource or Page is added, and no existing global-search posture changes in this slice. - **Destructive actions**: No destructive action is added or changed. This slice does not introduce new Filament actions. - **Asset strategy**: No new assets are planned. Deployment expectations remain unchanged, including `cd apps/platform && php artisan filament:assets` only when future UI work introduces registered assets. - **Testing plan**: Prove the slice with focused Pest unit coverage for catalog and resolver rules plus focused feature coverage for logical resolution, findings-derived evidence composition, and tenant review composition. ## UI / Surface Guardrail Plan - **Guardrail scope**: no operator-facing surface change - **Native vs custom classification summary**: `N/A` - **Shared-family relevance**: evidence viewers, tenant review detail composition, governance summaries - **State layers in scope**: detail - **Handling modes by drift class or surface**: `report-only` - **Repository-signal treatment**: `report-only` - **Special surface test profiles**: `standard-native-filament` - **Required tests or manual smoke**: `functional-core` - **Exception path and spread control**: none planned; any later UI adoption stays in a follow-through slice - **Active feature PR close-out entry**: `Guardrail` ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes - **Systems touched**: findings-derived evidence composition, evidence snapshot lookup, tenant review composition, tenant review section rendering inputs - **Shared abstractions reused**: existing evidence composition paths, existing tenant review composition paths, existing governance support types, new shared `CanonicalControlCatalog` and `CanonicalControlResolver` - **New abstraction introduced? why?**: yes. A bounded catalog plus resolver layer is required because existing builders only know provider subjects or local evidence context and cannot safely share one control identity. - **Why the existing abstraction was sufficient or insufficient**: existing builders are sufficient for surface-specific formatting, but insufficient for cross-domain control identity, detectability semantics, and provider-neutral vocabulary. - **Bounded deviation / spread control**: none. Downstream consumers must call the shared resolver rather than add local registries or fallback labels. ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes - **Provider-owned seams**: Microsoft workload labels, subject-family identifiers, signal keys, supported-context bindings - **Platform-core seams**: canonical control key, control domain, control subdomain, control class, detectability class, evaluation strategy, evidence archetypes, artifact suitability, historical status - **Neutral platform terms / contracts preserved**: canonical control, provider binding, governed subject, detectability class, evaluation strategy, evidence archetype, artifact suitability - **Retained provider-specific semantics and why**: Microsoft binding metadata remains provider-specific because the current product truth is Microsoft-first, but it remains secondary to the canonical control identity. - **Bounded extraction or follow-up path**: none in this slice; future provider expansion can layer on the same binding model if and when a second concrete provider exists ## Constitution Check *GATE: Passed before Phase 0 research. Re-check after Phase 1 design: still passed with no new persistence, no new operator surface, no Graph path, and no auth-plane drift.* | Gate | Status | Plan Notes | |------|--------|------------| | Inventory-first / read-write separation | PASS | This slice is read-focused and in-process only. No new write, preview, or operator mutation flow is introduced. | | RBAC, workspace isolation, tenant isolation | PASS | No new route or capability is added. Evidence and tenant review authorization remain the guarding surfaces for first-slice consumer metadata. | | Run observability / Ops-UX lifecycle | PASS | No new `OperationRun` type is introduced. Existing evidence and tenant review operation semantics remain unchanged. | | Shared pattern first | PASS | Evidence and tenant review builders remain the surface-specific composition paths; the shared catalog and resolver provide the missing control identity only. | | Proportionality / no premature abstraction | PASS | One catalog and one resolver are the narrowest correct shared layer. No DB CRUD, no plugin framework, and no new persistence are introduced. | | Persisted truth / behavioral state | PASS | No new table, entity, or lifecycle state is introduced. First-slice adoption is read-through only. | | Provider boundary | PASS | Microsoft semantics remain secondary binding metadata and do not replace the platform-core control vocabulary. | | Filament v5 / Livewire v4 contract | PASS | No new Filament surfaces or actions are added, and provider registration remains in `bootstrap/providers.php`. | | Test governance | PASS | Coverage stays in focused unit and feature lanes with no browser or heavy-governance expansion. | ## Test Governance Check - **Test purpose / classification by changed surface**: `Unit` for catalog and resolver semantics; `Feature` for findings-derived evidence composition, logical resolution, and tenant review composition - **Affected validation lanes**: `fast-feedback`, `confidence` - **Why this lane mix is the narrowest sufficient proof**: The core risk is semantic drift, not browser behavior. Unit tests prove deterministic catalog and binding rules; feature tests prove first-slice consumers use the shared contract instead of local labels. - **Narrowest proving command(s)**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Governance/CanonicalControlCatalogTest.php tests/Unit/Governance/CanonicalControlResolverTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/CanonicalControlResolutionIntegrationTest.php tests/Feature/Evidence/EvidenceSnapshotCanonicalControlReferenceTest.php tests/Feature/TenantReview/TenantReviewCanonicalControlReferenceTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - **Fixture / helper / factory / seed / context cost risks**: Minimal. Use config-seeded catalog fixtures and existing evidence or tenant review factories only where the downstream consumer proof needs persisted context. - **Expensive defaults or shared helper growth introduced?**: No. The catalog remains config-backed and in-process by default. - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: `standard-native-filament` relief; the slice does not add or materially refactor a Filament screen - **Closing validation and reviewer handoff**: Reviewers should verify that no Graph client or sync job changed, that first-slice adoption is limited to findings-derived evidence and tenant review composition, that unresolved and ambiguous outcomes never guess, and that provider-specific labels never replace canonical control vocabulary. - **Budget / baseline / trend follow-up**: none expected - **Review-stop questions**: Did any change widen consumer adoption beyond the intended first slice? Did any change introduce Graph or sync behavior? Did any feature-local control registry or fallback label appear? Did any contract field drift from the data model or seeded metadata? - **Escalation path**: `document-in-feature` - **Active feature PR close-out entry**: `Guardrail` - **Why no dedicated follow-up spec is needed**: The slice is a bounded semantic foundation. Only future consumer expansion or a second provider would justify a wider follow-up spec. ## Project Structure ### Documentation (this feature) ```text specs/236-canonical-control-catalog-foundation/ ├── spec.md ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md ├── contracts/ │ └── canonical-control-catalog.logical.openapi.yaml └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ │ ├── Models/ │ │ ├── EvidenceSnapshot.php │ │ ├── EvidenceSnapshotItem.php │ │ └── TenantReview.php │ ├── Services/ │ │ ├── Evidence/ │ │ │ ├── EvidenceSnapshotResolver.php │ │ │ ├── EvidenceSnapshotService.php │ │ │ └── Sources/ │ │ │ └── FindingsSummarySource.php │ │ └── TenantReviews/ │ │ ├── TenantReviewComposer.php │ │ ├── TenantReviewSectionFactory.php │ │ └── TenantReviewService.php │ └── Support/ │ └── Governance/ │ ├── GovernanceDomainKey.php │ └── Controls/ ├── config/ │ └── canonical_controls.php └── tests/ ├── Feature/ │ ├── Evidence/ │ │ └── EvidenceSnapshotCanonicalControlReferenceTest.php │ ├── Governance/ │ │ └── CanonicalControlResolutionIntegrationTest.php │ └── TenantReview/ │ └── TenantReviewCanonicalControlReferenceTest.php └── Unit/ └── Governance/ ├── CanonicalControlCatalogTest.php └── CanonicalControlResolverTest.php ``` **Structure Decision**: Keep the slice entirely inside the existing Laravel runtime in `apps/platform`. The new structure is limited to one config-backed seed file and one small `App\Support\Governance\Controls` namespace, while first-slice consumer adoption stays inside existing Evidence and TenantReview services plus focused Pest tests. ## Complexity Tracking No constitutional violation is planned. No complexity exception is currently required. | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | — | — | — | ## Proportionality Review - **Current operator problem**: The same governance objective is still described differently across findings-derived evidence and tenant review composition, which prevents stable control identity and honest detectability semantics. - **Existing structure is insufficient because**: current governed-subject and workload metadata explain provider context, not the higher-order control objective or what kind of proof the product can honestly claim. - **Narrowest correct implementation**: add one config-backed canonical control catalog plus one shared resolver and adopt it only in findings-derived evidence composition and tenant review composition. - **Ownership cost created**: maintain the seed catalog, keep binding rules deterministic, and preserve regression tests that block local control-family drift. - **Alternative intentionally rejected**: feature-local mappings and a DB-backed control authoring system. The first preserves fragmentation; the second imports unnecessary lifecycle, UI, and persistence complexity before the foundation is proven. - **Release truth**: current-release truth with deliberate preparation for later consumer expansion ## Phase 0 Research Summary - The first catalog should be product-seeded and config-backed, not DB-managed. - Platform-core canonical controls must remain separate from provider-owned Microsoft bindings. - Ambiguity must resolve as explicit `ambiguous`, never as a guessed winner. - Detectability, evaluation strategy, and evidence archetypes belong directly on the control definition. - First-slice adoption should be read-through rather than persistence-first. - The seed catalog should stay bounded to a small set of high-value governance control families. ## Phase 1 Design Summary - `research.md` records the architectural decisions that keep the slice narrow and provider-neutral at the control core. - `data-model.md` defines the three core shapes: `CanonicalControlDefinition`, `MicrosoftSubjectBinding`, and `CanonicalControlResolutionResult`. - `contracts/canonical-control-catalog.logical.openapi.yaml` defines the shared internal contract for catalog listing and control resolution. - `quickstart.md` defines the narrow validation order and the intended code areas for the first slice. - `tasks.md` sequences the work from seed catalog and resolver foundation through findings-derived evidence and tenant review adoption. ## Phase 1 — Agent Context Update Run after artifact generation: - `.specify/scripts/bash/update-agent-context.sh copilot` ## Implementation Strategy ### Phase A — Seed the canonical control catalog **Goal**: Create one authoritative, product-seeded catalog with stable control keys and complete control metadata. | Step | File | Change | |------|------|--------| | A.1 | `apps/platform/config/canonical_controls.php` | Add the bounded canonical control seed catalog and Microsoft binding metadata. | | A.2 | `apps/platform/app/Support/Governance/Controls/CanonicalControlDefinition.php` and related enums/value objects | Model canonical control metadata, detectability classes, evaluation strategies, evidence archetypes, artifact suitability, and historical status. | | A.3 | `apps/platform/app/Support/Governance/Controls/CanonicalControlCatalog.php` | Load, validate, and expose stable control definitions and binding metadata deterministically. | ### Phase B — Implement provider-owned binding and shared resolution semantics **Goal**: Resolve canonical controls through one shared contract without letting Microsoft metadata become the control model. | Step | File | Change | |------|------|--------| | B.1 | `apps/platform/app/Support/Governance/Controls/MicrosoftSubjectBinding.php` | Model Microsoft workload, subject-family, signal, and supported-context binding metadata. | | B.2 | `apps/platform/app/Support/Governance/Controls/CanonicalControlResolutionRequest.php` and `CanonicalControlResolutionResult.php` | Define the shared request and response primitives for `resolved`, `unresolved`, and `ambiguous` outcomes. | | B.3 | `apps/platform/app/Support/Governance/Controls/CanonicalControlResolver.php` | Implement deterministic context-aware resolution, unresolved handling, and explicit ambiguity. | ### Phase C — Adopt the shared contract in the first-slice consumers **Goal**: Move current-release consumer adoption onto the shared control contract without widening the slice. | Step | File | Change | |------|------|--------| | C.1 | `apps/platform/app/Services/Evidence/Sources/FindingsSummarySource.php` and `apps/platform/app/Services/Evidence/EvidenceSnapshotService.php` | Resolve canonical control references inside findings-derived evidence composition. | | C.2 | `apps/platform/app/Services/Evidence/EvidenceSnapshotResolver.php` and `apps/platform/app/Models/EvidenceSnapshotItem.php` | Preserve transient resolved control metadata during evidence lookup and item payload consumption without introducing new canonical-control persistence ownership. | | C.3 | `apps/platform/app/Services/TenantReviews/TenantReviewComposer.php`, `TenantReviewSectionFactory.php`, and `TenantReviewService.php` | Reuse the shared resolver during tenant review composition and keep persistence derived. | ### Phase D — Validate contract shape, scope discipline, and negative constraints **Goal**: Prove semantic correctness while keeping the slice narrow. | Step | File | Change | |------|------|--------| | D.1 | `apps/platform/tests/Unit/Governance/CanonicalControlCatalogTest.php` and `CanonicalControlResolverTest.php` | Prove stable keys, metadata completeness, multi-binding behavior, unresolved outcomes, ambiguity, and retired-control handling. | | D.2 | `apps/platform/tests/Feature/Governance/CanonicalControlResolutionIntegrationTest.php` | Prove the logical contract shape stays aligned to the seed catalog and resolver rules. | | D.3 | `apps/platform/tests/Feature/Evidence/EvidenceSnapshotCanonicalControlReferenceTest.php` and `apps/platform/tests/Feature/TenantReview/TenantReviewCanonicalControlReferenceTest.php` | Prove first-slice consumers adopt the shared contract without local registries or fallback labels. | | D.4 | `specs/236-canonical-control-catalog-foundation/quickstart.md` and `tasks.md` | Keep validation commands, paths, and no-Graph/no-sync guardrails explicit. | ## Risks and Mitigations - **Provider-shaped drift**: Microsoft labels may accidentally become the canonical vocabulary. Mitigation: keep canonical control definitions and bindings structurally separate and test for provider-neutral keys and labels. - **Consumer-scope drift**: It is easy to widen adoption into baseline, exception, or report surfaces prematurely. Mitigation: keep first-slice scope explicitly limited to findings-derived evidence and tenant review composition in the plan, spec, tasks, and validation notes. - **Contract-shape drift**: The contract, data model, and seed metadata can diverge. Mitigation: keep the logical contract small, test it directly, and align fields such as `operator_description`, `binding_context`, and supported contexts explicitly. - **Graph creep**: Future-looking catalog work can attract provider sync ideas too early. Mitigation: keep a documented no-Graph/no-sync guardrail in tasks and review focus. ## Post-Design Re-check The feature remains constitution-compliant, Filament v5 and Livewire v4 compliant, and narrow. It introduces no new persistence, no new operator-facing page, no new Graph path, and no new operation type. The plan, research, data model, quickstart, contract, and tasks now align on one config-seeded catalog, one shared resolver, one provider-boundary rule, and one bounded first-slice consumer scope.