TenantAtlas/specs/236-canonical-control-catalog-foundation/plan.md
ahmido 6a5b8a3a11
Some checks failed
Main Confidence / confidence (push) Failing after 50s
feat: canonical control catalog foundation (#272)
## 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
2026-04-24 12:26:02 +00:00

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: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)

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