251 lines
21 KiB
Markdown
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.
|