150 lines
12 KiB
Markdown
150 lines
12 KiB
Markdown
# Implementation Report: Spec 417 - Canonical Identity Engine
|
|
|
|
## Preflight
|
|
|
|
- Branch: `417-canonical-identity-engine`
|
|
- Starting HEAD: `332f6325 feat: add tenantpilot agent skill layer v1 (#483)`
|
|
- Starting dirty state: `specs/417-canonical-identity-engine/` untracked as the active spec artifact set.
|
|
- Dirty-state assessment: active Spec 417 preparation artifacts only; no runtime code was dirty before implementation.
|
|
- Activated skills: `spec-kit-implementation-loop`, `pest-testing`, `tenantpilot-spec-readiness-gate`, `tenantpilot-workspace-scope-safety`, `tenantpilot-evidence-anchor-contract`, `tenantpilot-tcm-cutover-guard`.
|
|
- Hard-gate stop conditions before implementation: none observed. Coverage v2 remains inactive; no UI/customer proof surface is in scope.
|
|
|
|
## Dependency Context
|
|
|
|
- Spec 414: completed/validated context only; not modified.
|
|
- Spec 415: completed/validated context only; not modified.
|
|
- Existing canonical identity storage: `tenant_configuration_resources.canonical_resource_key` remains the single persisted canonical key truth.
|
|
- Browser proof decision: `N/A - no rendered UI surface changed`.
|
|
|
|
## Implementation Summary
|
|
|
|
- Added a bounded canonical identity engine for the initial eight Coverage v2 resource types.
|
|
- Added `CanonicalKeyKind` values without display-name/name-only stable key kinds.
|
|
- Added identity strategy, resolver, secondary-key, diagnostics, and same-scope conflict evaluation services under `App\Services\TenantConfiguration`.
|
|
- Extended `tenant_configuration_resources` with additive identity metadata: `canonical_key_kind`, `identity_strategy`, `source_identity`, `secondary_identity_keys`, `identity_diagnostics`, and `identity_evaluated_at`.
|
|
- Kept `canonical_resource_key` as the single persisted canonical key truth; no `canonical_key` column or duplicate key truth was added.
|
|
- Updated `CoverageResourceUpserter` to consume resolver/evaluator output instead of hardcoding `id`/`sourceId`.
|
|
- Updated `CoverageEvidenceWriter` to preserve resource identity/claim state instead of resetting to resource-type defaults.
|
|
- Updated `ClaimGuard` to block `identity_conflict`, `missing_external_id`, and `unsupported_identity`, and to limit/block `derived` identity unless explicitly allowed.
|
|
- Kept Coverage v2 inactive; no UI, route, navigation, report, review, restore, customer-output, or browser-visible activation was added.
|
|
|
|
## Identity Strategy Matrix
|
|
|
|
| Canonical type | Strategy | Stable ID posture | Derived/experimental posture | Default claim consequence |
|
|
| --- | --- | --- | --- | --- |
|
|
| `deviceAndAppManagementAssignmentFilter` | `tcm.assignment_filter.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `deviceEnrollmentLimitRestriction` | `tcm.device_enrollment_limit_restriction.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `deviceEnrollmentPlatformRestriction` | `tcm.device_enrollment_platform_restriction.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `deviceEnrollmentStatusPageWindows10` | `tcm.device_enrollment_status_page_windows10.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `appProtectionPolicyAndroid` | `tcm.app_protection_policy_android.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `appProtectionPolicyiOS` | `tcm.app_protection_policy_ios.v1` | TCM/provider IDs stable | source/derived composite allowed | derived limited |
|
|
| `notificationMessageTemplate` | `graph.notification_message_template.v1` | Graph/template IDs stable | source/derived composite allowed | no certification by default |
|
|
| `roleScopeTag` | `graph_beta.role_scope_tag.v1` | beta IDs are experimental, not stable proof | experimental source key resolves as derived | internal-only by default |
|
|
|
|
## Schema And Persistence
|
|
|
|
- Migration added: `apps/platform/database/migrations/2026_06_26_000417_extend_tenant_configuration_resource_identity.php`.
|
|
- Added PostgreSQL check constraint for `canonical_key_kind`.
|
|
- Added PostgreSQL JSONB object constraints for `source_identity`, `secondary_identity_keys`, and `identity_diagnostics`.
|
|
- Added targeted indexes for scope/type/identity-state and resource-type/key-kind lookup paths.
|
|
- No speculative JSONB GIN index was added.
|
|
- Tombstone behavior is deferred; no tombstone field or active delete/drift workflow is implemented in this slice.
|
|
|
|
## Scope, Claim, And Evidence Safety
|
|
|
|
- Scope tuple remains `workspace_id`, `managed_environment_id`, `provider_connection_id`, `resource_type_id`, and `canonical_resource_key`.
|
|
- Provider connections are still validated against the same workspace and managed environment before upsert/capture.
|
|
- Stable ID rename updates the same same-scope resource row.
|
|
- Duplicate display names with different stable IDs remain separate rows.
|
|
- Display-name-only payloads resolve to `missing_external_id`, not `stable`; repeated same-scope display-only observations are promoted to `identity_conflict` instead of merging by secondary/display fingerprint.
|
|
- Unsupported identity observations are promoted to `identity_conflict` on same-scope fallback-key collision instead of merging by unsupported fallback key.
|
|
- Same-scope unsafe derived identity collisions mark existing/incoming resources as `identity_conflict` and `claim_blocked`.
|
|
- Cross-workspace, cross-managed-environment, and cross-provider resources do not merge because the existing scope tuple remains part of identity uniqueness.
|
|
- Diagnostics and secondary keys are redacted and bounded; no raw provider payloads, tokens, secrets, cookies, authorization headers, private keys, certificates, or full provider response dumps are persisted in identity diagnostics.
|
|
- No fallback-to-latest evidence path, v1-to-v2 adapter, dual-write path, old snapshot promotion, or old v1 gap taxonomy was introduced.
|
|
|
|
## Product Surface Close-Out
|
|
|
|
- UI Surface Impact: none.
|
|
- Product Surface Impact: `N/A - no rendered product surface changed`.
|
|
- Browser smoke result: `N/A - no rendered UI surface changed`.
|
|
- Human Product Sanity: `N/A - no product surface changed`; visible complexity outcome is neutral.
|
|
- Livewire v4 compliance: Livewire v4 baseline unchanged; no Livewire code changed.
|
|
- Provider registration location: no panel provider change; Laravel 12 providers remain in `apps/platform/bootstrap/providers.php`.
|
|
- Global search posture: no Filament resource/global search change.
|
|
- Destructive/high-impact actions: none added.
|
|
- Asset strategy: no assets registered; `filament:assets` is not required by this spec.
|
|
- No completed historical spec was rewritten or stripped of validation, task, smoke, browser, or review history.
|
|
|
|
## Validation
|
|
|
|
- PASS: `php -l` syntax sweep for tracked and untracked changed PHP files.
|
|
- PASS: `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec417` (24 passed, 1 PostgreSQL-only skipped, 162 assertions).
|
|
- PASS: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/TenantConfiguration` (35 passed, 170 assertions).
|
|
- PASS: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantConfiguration` (35 passed, 8 PostgreSQL-only skipped, 190 assertions).
|
|
- PASS: `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest -c phpunit.pgsql.xml tests/Feature/TenantConfiguration` (43 passed, 203 assertions).
|
|
- PASS: `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`.
|
|
- PASS: `git diff --check`.
|
|
- PASS: untracked-file trailing-whitespace scan.
|
|
|
|
## Analysis And Fix Loop
|
|
|
|
- Iteration 1 finding: secondary-key and diagnostics builders redacted nested values without preserving key context, so sensitive scalar fields were not redacted. Fixed by redacting with field-name context; regression tests pass.
|
|
- Iteration 2 finding: PostgreSQL JSONB object constraints rejected empty PHP arrays encoded as `[]`. Fixed by normalizing empty metadata/defaults to JSON objects; PostgreSQL lane passes.
|
|
- Post-implementation scope finding: optional tombstone timestamp was unnecessary without tombstone behavior. Fixed by removing the unused field and documenting tombstone deferral.
|
|
- Manual final-review finding: repeated same-scope display-name-only payloads and unsupported identity observations could merge by fallback candidate keys even though they were not marked stable. Fixed by treating repeated `missing_external_id` and `unsupported_identity` candidate collisions as `identity_conflict`, marking existing candidates `claim_blocked`, generating a separate conflict key for the new unsafe observation, normalizing empty `source_metadata` to JSON object form, and adding feature regressions for both paths.
|
|
- Remaining confirmed in-scope findings: none.
|
|
|
|
## Files Changed
|
|
|
|
- `apps/platform/app/Models/TenantConfigurationResource.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CanonicalIdentityResolver.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CanonicalIdentityResult.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/ClaimGuard.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CoverageResourceIdentityEvaluator.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CoverageResourceUpserter.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/CoverageSecondaryKeyBuilder.php`
|
|
- `apps/platform/app/Services/TenantConfiguration/IdentityConflictDiagnosticsBuilder.php`
|
|
- `apps/platform/app/Support/TenantConfiguration/CanonicalKeyKind.php`
|
|
- `apps/platform/database/factories/TenantConfigurationResourceFactory.php`
|
|
- `apps/platform/database/migrations/2026_06_26_000417_extend_tenant_configuration_resource_identity.php`
|
|
- `apps/platform/tests/Feature/TenantConfiguration/Spec417CanonicalIdentityPersistenceTest.php`
|
|
- `apps/platform/tests/Feature/TenantConfiguration/Spec417CoverageResourceIdentityUpsertTest.php`
|
|
- `apps/platform/tests/Feature/TenantConfiguration/Spec417IdentityClaimGuardFeatureTest.php`
|
|
- `apps/platform/tests/Feature/TenantConfiguration/Spec417IdentityConflictScopeTest.php`
|
|
- `apps/platform/tests/Feature/TenantConfiguration/Spec417IdentityNoLegacyNoUiActivationTest.php`
|
|
- `apps/platform/tests/Unit/Support/TenantConfiguration/ClaimGuardTest.php`
|
|
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec417CanonicalIdentityResolverTest.php`
|
|
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec417CoverageIdentityStrategyRegistryTest.php`
|
|
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec417CoverageSecondaryKeyBuilderTest.php`
|
|
- `apps/platform/tests/Unit/Support/TenantConfiguration/Spec417IdentityConflictDiagnosticsTest.php`
|
|
- `specs/417-canonical-identity-engine/implementation-report.md`
|
|
- `specs/417-canonical-identity-engine/tasks.md`
|
|
|
|
## Deployment Impact
|
|
|
|
- Migrations: yes, additive identity metadata/check constraints/indexes on `tenant_configuration_resources`.
|
|
- Environment variables: none.
|
|
- Queues/workers: no new queue or OperationRun path.
|
|
- Scheduler: no change.
|
|
- Storage/volumes: no change.
|
|
- Assets: no change.
|
|
- Staging/production: run migrations before any later Coverage v2 activation; validate PostgreSQL migration in staging.
|
|
|
|
## Deferred Work
|
|
|
|
- Tombstone/delete workflow remains out of scope.
|
|
- Customer/operator identity diagnostics UI remains out of scope.
|
|
- Coverage v2 activation, legacy cutover/removal, compare/render/restore/report/customer claims remain future specs.
|
|
|
|
## Final Gate Result
|
|
|
|
- Spec Readiness Gate: PASS.
|
|
- Implementation Scope Gate: PASS.
|
|
- Test Gate: PASS.
|
|
- Browser Smoke Test Gate: PASS as not applicable, `N/A - no rendered UI surface changed`.
|
|
- Post-Implementation Analysis Gate: PASS.
|
|
- Merge Readiness Gate: PASS; ready for manual review/merge.
|