16 KiB
Implementation Plan: Spec 417 - Canonical Identity Engine
Branch: 417-canonical-identity-engine | Date: 2026-06-26 | Spec: specs/417-canonical-identity-engine/spec.md
Input: Feature specification from /specs/417-canonical-identity-engine/spec.md
Summary
Add a bounded canonical identity engine for Coverage v2 resources captured by the Spec 415 pipeline. The implementation should define per-resource-type identity strategies for the initial eight Coverage v2 resource types, resolve deterministic canonical identities without relying on display names as stable truth, persist one canonical key truth plus key kind and redacted diagnostics, integrate resolver output into resource upsert and Claim Guard, and keep Coverage v2 inactive with no UI/customer-output activation.
Technical Context
Language/Version: PHP 8.4.x, Laravel 12.x
Primary Dependencies: Laravel Eloquent, PostgreSQL, existing TenantConfiguration services, existing OperationRun service if evaluation becomes separate from capture
Storage: PostgreSQL; existing tenant_configuration_resources, tenant_configuration_resource_evidence, and tenant_configuration_resource_types tables
Testing: Pest 4 / PHPUnit 12 via Sail
Validation Lanes: fast-feedback, confidence, pgsql where migration/constraint/index behavior is involved; browser N/A unless scope is amended
Target Platform: Laravel Sail locally, Dokploy/container deployment for staging/production
Project Type: Laravel monolith under apps/platform
Performance Goals: identity resolution is deterministic and DB-bounded; no Graph/provider calls beyond existing capture path; no new render-time work
Constraints: no UI activation, no v1/v2 compatibility adapter, no tenant_id ownership truth, no raw payload/secrets in diagnostics/OperationRun/audit, no duplicate canonical key truth
Scale/Scope: initial eight Coverage v2 resource types only; future resource packs are separate specs
UI / Surface Guardrail Plan
- Guardrail scope: no operator-facing surface change.
- Affected routes/pages/actions/states/navigation/panel/provider surfaces: N/A.
- No-impact class, if applicable: backend/internal runtime and data-safety only.
- Native vs custom classification summary: N/A.
- Shared-family relevance: evidence/claim safety only; no rendered interaction family.
- State layers in scope: internal resource identity and claim state only.
- Audience modes in scope: no rendered customer/operator/support UI. Internal diagnostics must remain support/platform data if later exposed.
- Decision/diagnostic/raw hierarchy plan: N/A for UI; raw payloads/source keys/diagnostics remain technical data.
- Raw/support gating plan: N/A now; any future exposure must be capability-gated and Product Surface-reviewed.
- One-primary-action / duplicate-truth control: no UI action; ensure one canonical persisted identity key truth.
- Handling modes by drift class or surface: hard-stop if UI, route, navigation, report, review, restore, or customer-output work appears.
- Repository-signal treatment: review-mandatory for any changed guarded UI/product file path; expected none.
- Special surface test profiles: N/A.
- Required tests or manual smoke: functional-core, persistence, scope, claim guard, no-UI static guard.
- Exception path and spread control: none.
- Active feature PR close-out entry: Guardrail / Exception / Smoke Coverage.
- UI/Productization coverage decision: No UI surface impact.
- Coverage artifacts to update: none.
- No-impact rationale: Canonical identity is internal runtime/data truth needed before later UI/customer activation.
- Navigation / Filament provider-panel handling: no panel/provider change.
- Screenshot or page-report need: no.
Product Surface Contract Plan
- Product Surface Contract reference:
docs/product/standards/product-surface-contract.md. - No-legacy posture: canonical replacement; no compatibility exception.
- Page archetype and surface budget plan: N/A.
- Technical Annex and deep-link demotion plan: no rendered product view. OperationRun, raw evidence, IDs, source keys, payloads, and diagnostics remain internal data.
- Canonical status vocabulary plan: N/A for product-facing UI; internal
IdentityStateremains non-rendered domain state. - Product Surface exceptions: none.
- Browser verification plan:
N/A - no rendered UI surface changed. - Human Product Sanity plan: N/A.
- Visible complexity outcome target: neutral.
- Implementation report target:
specs/417-canonical-identity-engine/implementation-report.md.
Filament / Livewire / Deployment Posture
- Livewire v4 compliance: Livewire v4.x baseline confirmed; no Livewire code expected.
- Panel provider registration location: no panel change; Laravel 12 panel providers remain
apps/platform/bootstrap/providers.php. - Global search posture: no resource changed or added; N/A.
- Destructive/high-impact action posture: none expected. If a separate identity re-evaluation start action is introduced, it is high-impact and must be authorized, audited, OperationRun-backed, confirmation-protected when user-triggered, and spec-amended for UI if rendered.
- Asset strategy: no assets;
filament:assetsnot required by this spec. - Testing plan: unit tests for strategy/resolver/secondary keys/diagnostics/claim guard; feature tests for persistence/upsert/conflict/scope/no-legacy/no-UI; PostgreSQL lane for migration/index/constraint behavior where needed.
- Deployment impact: expected migration(s) for identity columns/indexes; no env vars, scheduler, storage, assets, or new queue worker if evaluation stays in existing capture. If a new evaluation job is added, queue impact must be documented.
Shared Pattern & System Fit
- Cross-cutting feature marker: yes, internal evidence/claim/capture semantics.
- Systems touched: TenantConfiguration resource persistence,
CoverageResourceUpserter,CoverageEvidenceWriter,GenericContentEvidenceCaptureService,ClaimGuard,ResourceTypeRegistry, tests/factories. - Shared abstractions reused: existing TenantConfiguration services,
CoveragePayloadRedactor, existing OperationRun lifecycle if evaluation becomes operation-backed. - New abstraction introduced? why?: bounded identity strategy/resolver/evaluator helpers. Existing upsert is insufficient because identity strategy varies by resource type/source class and must drive claim consequences.
- Why the existing abstraction was sufficient or insufficient: Existing resource type registry is sufficient for the initial resource set; current upsert is insufficient because it hardcodes
id/sourceId, throws on missing ID, and defaults identity to stable. - Bounded deviation / spread control: do not introduce a multi-provider framework or UI presenter. Keep identity services inside
App\Services\TenantConfiguration/App\Support\TenantConfiguration.
OperationRun UX Impact
- Touches OperationRun start/completion/link UX?: no new UX expected.
- Central contract reused: existing Spec 415 capture OperationRun lifecycle if identity evaluation runs inside capture.
- Delegated UX behaviors: N/A.
- Surface-owned behavior kept local: none.
- Queued DB-notification policy: N/A; no new DB notifications.
- Terminal notification path: existing central lifecycle mechanism only if new operation-backed evaluation is added.
- Exception path: none.
If implementation adds a distinct tenant_configuration.identity_evaluation path, add or extend the operation catalog/type with service-owned transitions, numeric-only summary counts, no raw payloads in run context/messages, no custom terminal notifications, and tests for lifecycle, scope, idempotency, capability access, readonly denial, wrong-scope 404, and missing-capability 403. Do not add a UI start surface without amending spec/plan/tasks first.
Provider Boundary & Portability Fit
- Shared provider/platform boundary touched?: yes.
- Provider-owned seams: Graph/TCM payload field names, source IDs, source endpoint/version metadata, beta/fallback source classes.
- Platform-core seams: workspace/managed-environment/provider-connection ownership, canonical identity state, canonical key kind, claim state, no-customer-claim behavior.
- Neutral platform terms / contracts preserved: provider connection, managed environment, resource type, canonical resource key, identity state, claim state.
- Retained provider-specific semantics and why: Microsoft Graph/TCM field extraction remains per-strategy because Microsoft is the current concrete provider and the initial resource types are Microsoft/Intune-backed.
- Bounded extraction or follow-up path: document-in-feature; future providers/resource packs add strategies, not a speculative provider framework.
Constitution Check
- Inventory-first / evidence-first: PASS. Resource identity is internal observed-resource truth; evidence payload truth stays in append-only evidence rows.
- Read/write separation: PASS. No provider write/restore/remediation behavior.
- Graph contract path: PASS. This spec should not add new Graph calls; existing capture remains through
ProviderGateway/Graph contract path. - Deterministic capabilities: N/A for new capabilities unless separate re-evaluation start path is added.
- RBAC-UX: PASS with conditions. Identity evaluation inherits capture authorization unless a new start path is added; any new start path requires 404/403 semantics and capability tests.
- Workspace isolation: PASS with required tests for workspace/environment/provider scope and no cross-scope merge.
- OperationRun: PASS with conditions. No new OperationRun path expected; if added, service-owned lifecycle and summary-count rules apply.
- Evidence/currentness: PASS. Identity diagnostics must not become customer proof and must not fallback to latest evidence.
- Customer output: PASS. No customer output or download surface.
- Provider boundary: PASS with strategy-local provider fields only.
- Product Surface: PASS. No rendered UI; stop if UI becomes necessary.
- Test governance: PASS. Unit/feature/pgsql lanes named; browser/heavy-governance N/A.
- Proportionality: PASS with bounded exception. Identity engine adds structural complexity because current release needs claim-safety before Coverage v2 activation.
- No premature abstraction: PASS with bounded exception. Eight concrete resource types, existing capture/upsert, and Claim Guard provide real consumers.
- Persisted truth: PASS. Canonical identity is durable resource truth and claim-safety input.
- Behavioral state: PASS. Identity states change claim behavior and conflict handling.
- No legacy / pre-production lean: PASS. No alias, fallback reader, dual write, v1 adapter, or
tenant_id.
Test Governance Check
- Test purpose / classification by changed surface: Unit for pure identity logic; Feature for persistence/upsert/claim guard/no-legacy/no-UI; PostgreSQL for schema/index/constraint behavior.
- Affected validation lanes: fast-feedback, confidence, pgsql.
- Why this lane mix is the narrowest sufficient proof: service and DB behavior are the risk; browser cannot add value without UI changes.
- Narrowest proving command(s):
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfigurationcd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantConfigurationcd apps/platform && ./vendor/bin/sail php vendor/bin/pest -c phpunit.pgsql.xml tests/Feature/TenantConfiguration
- Fixture / helper / factory / seed / context cost risks: keep new TenantConfiguration factory states opt-in; do not broaden unrelated workspace/provider setup.
- Expensive defaults or shared helper growth introduced?: no expected default broadening.
- Heavy-family additions, promotions, or visibility changes: none.
- Surface-class relief / special coverage rule: N/A - no rendered UI.
- Closing validation and reviewer handoff: implementation report must include exact tests run, any PostgreSQL migration smoke, dirty state, and no-browser rationale.
- Budget / baseline / trend follow-up: none expected; document if TenantConfiguration lane cost materially increases.
- Review-stop questions: lane fit, no hidden browser/heavy family, no fixture default broadening, no old v1 terms, no duplicate canonical key truth.
- Escalation path: document-in-feature.
- Active feature PR close-out entry: Guardrail / Exception / Smoke Coverage.
- Why no dedicated follow-up spec is needed: identity hardening is contained to the current Coverage v2 resource/capture/claim path.
Project Structure
Documentation (this feature)
specs/417-canonical-identity-engine/
├── spec.md
├── plan.md
├── tasks.md
└── checklists/
└── requirements.md
Source Code (likely affected in later implementation)
apps/platform/app/
├── Models/
│ └── TenantConfigurationResource.php
├── Services/TenantConfiguration/
│ ├── CanonicalIdentityResolver.php
│ ├── CoverageIdentityStrategyRegistry.php
│ ├── CoverageResourceIdentityEvaluator.php
│ ├── CoverageResourceUpserter.php
│ ├── CoverageEvidenceWriter.php
│ ├── CoverageSecondaryKeyBuilder.php
│ ├── GenericContentEvidenceCaptureService.php
│ └── IdentityConflictDiagnosticsBuilder.php
└── Support/TenantConfiguration/
├── CanonicalKeyKind.php
└── IdentityState.php
apps/platform/database/
├── factories/
│ └── TenantConfigurationResourceFactory.php
└── migrations/
└── 2026_06_26_000417_extend_tenant_configuration_resource_identity.php
apps/platform/tests/
├── Unit/Support/TenantConfiguration/
└── Feature/TenantConfiguration/
Implementation Phases
Phase 0 - Preflight And Dependency Confirmation
Confirm branch, HEAD, dirty state, completed Spec 414/415 guardrail, current code paths, current schema, and no existing 417 package/branch collision.
Phase 1 - Identity Schema And Strategy Shape
Define the minimal persisted identity shape. Use existing canonical_resource_key as the canonical key. If implementation proves it cannot serve, stop and amend spec.md, plan.md, and tasks.md before replacing it; do not treat replacement as an in-loop implementation choice. Add key kind, external/source identity, strategy identifier, secondary keys, diagnostics, evaluated timestamp, and optional tombstone timestamp only where needed.
Phase 2 - Resolver And Strategy Implementation
Implement the strategy registry and resolver for the initial eight resource types. Extract stable IDs first, then documented source IDs/composites, then derived only where allowed, and unsupported/conflict/missing states otherwise.
Phase 3 - Upsert, Conflict, And Claim Guard Integration
Update upsert/evidence writing to consume identity resolver output, detect same-scope unsafe collisions, preserve rename behavior, prevent cross-scope merges, and block/limit unsafe claims through Claim Guard.
Phase 4 - Redaction, No-Legacy, And Static Guards
Ensure diagnostics/secondary keys are redacted, no v1 gap terms or adapters are introduced, no tenant_id ownership appears, and no UI/customer claim surface is added.
Phase 5 - Validation And Implementation Report
Run focused validation, document schema/strategy matrix, scope proof, redaction proof, no-legacy/no-UI proof, tests, deployment impact, and deferred work.
Complexity Tracking
Allowed complexity:
- Bounded identity strategy/resolver/evaluator services for existing resource types.
- A small key-kind value family with behavioral claim consequences.
- Additional columns/JSONB diagnostics on existing resource rows.
Rejected complexity:
- New identity conflict table by default.
- Generic multi-provider strategy framework.
- Runtime Product Surface framework or UI presenter.
- v1/v2 adapter, dual write, fallback reader, old gap taxonomy.
- Customer-facing identity UI or reports.
Deployment Impact
- Migrations: likely yes, additive identity fields/indexes/check constraints on
tenant_configuration_resources. - Env vars: none expected.
- Queues: none if identity runs inside existing capture. Queue impact only if a new identity re-evaluation job is added.
- Scheduler: none.
- Storage/volumes: none.
- Assets: none;
filament:assetsnot required. - Staging/production: run migrations before any later Coverage v2 activation; validate no provider connection/resource scope constraint issues on staging.