## Summary - add a config-seeded canonical control catalog plus shared resolution primitives and Microsoft subject bindings - propagate canonical control references into findings-derived evidence snapshots and tenant review composition - add the feature spec artifacts and focused Pest coverage, plus the supporting workspace and Sail helper adjustments included in this branch ## Testing - cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Governance/CanonicalControlCatalogTest.php tests/Unit/Governance/CanonicalControlResolverTest.php tests/Feature/Governance/CanonicalControlResolutionIntegrationTest.php tests/Feature/Evidence/EvidenceSnapshotCanonicalControlReferenceTest.php tests/Feature/TenantReview/TenantReviewCanonicalControlReferenceTest.php tests/Feature/PlatformRelocation/CommandModelSmokeTest.php - cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #272
21 KiB
Implementation Plan: Canonical Control Catalog Foundation
Branch: 236-canonical-control-catalog-foundation | Date: 2026-04-24 | Spec: 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:assetsonly 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
CanonicalControlCatalogandCanonicalControlResolver - 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:
Unitfor catalog and resolver semantics;Featurefor 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.phpcd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Governance/CanonicalControlResolutionIntegrationTest.php tests/Feature/Evidence/EvidenceSnapshotCanonicalControlReferenceTest.php tests/Feature/TenantReview/TenantReviewCanonicalControlReferenceTest.phpcd 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-filamentrelief; 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)
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)
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.mdrecords the architectural decisions that keep the slice narrow and provider-neutral at the control core.data-model.mddefines the three core shapes:CanonicalControlDefinition,MicrosoftSubjectBinding, andCanonicalControlResolutionResult.contracts/canonical-control-catalog.logical.openapi.yamldefines the shared internal contract for catalog listing and control resolution.quickstart.mddefines the narrow validation order and the intended code areas for the first slice.tasks.mdsequences 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.