TenantAtlas/specs/236-canonical-control-catalog-foundation/plan.md
2026-04-24 14:15:50 +02:00

251 lines
21 KiB
Markdown

# 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.