12 KiB
12 KiB
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_keyremains 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
CanonicalKeyKindvalues 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_resourceswith additive identity metadata:canonical_key_kind,identity_strategy,source_identity,secondary_identity_keys,identity_diagnostics, andidentity_evaluated_at. - Kept
canonical_resource_keyas the single persisted canonical key truth; nocanonical_keycolumn or duplicate key truth was added. - Updated
CoverageResourceUpserterto consume resolver/evaluator output instead of hardcodingid/sourceId. - Updated
CoverageEvidenceWriterto preserve resource identity/claim state instead of resetting to resource-type defaults. - Updated
ClaimGuardto blockidentity_conflict,missing_external_id, andunsupported_identity, and to limit/blockderivedidentity 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, andidentity_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, andcanonical_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, notstable; repeated same-scope display-only observations are promoted toidentity_conflictinstead of merging by secondary/display fingerprint. - Unsupported identity observations are promoted to
identity_conflicton same-scope fallback-key collision instead of merging by unsupported fallback key. - Same-scope unsafe derived identity collisions mark existing/incoming resources as
identity_conflictandclaim_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:assetsis not required by this spec. - No completed historical spec was rewritten or stripped of validation, task, smoke, browser, or review history.
Validation
- PASS:
php -lsyntax 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_idandunsupported_identitycandidate collisions asidentity_conflict, marking existing candidatesclaim_blocked, generating a separate conflict key for the new unsafe observation, normalizing emptysource_metadatato JSON object form, and adding feature regressions for both paths. - Remaining confirmed in-scope findings: none.
Files Changed
apps/platform/app/Models/TenantConfigurationResource.phpapps/platform/app/Services/TenantConfiguration/CanonicalIdentityResolver.phpapps/platform/app/Services/TenantConfiguration/CanonicalIdentityResult.phpapps/platform/app/Services/TenantConfiguration/ClaimGuard.phpapps/platform/app/Services/TenantConfiguration/CoverageEvidenceWriter.phpapps/platform/app/Services/TenantConfiguration/CoverageIdentityStrategyRegistry.phpapps/platform/app/Services/TenantConfiguration/CoverageResourceIdentityEvaluator.phpapps/platform/app/Services/TenantConfiguration/CoverageResourceUpserter.phpapps/platform/app/Services/TenantConfiguration/CoverageSecondaryKeyBuilder.phpapps/platform/app/Services/TenantConfiguration/IdentityConflictDiagnosticsBuilder.phpapps/platform/app/Support/TenantConfiguration/CanonicalKeyKind.phpapps/platform/database/factories/TenantConfigurationResourceFactory.phpapps/platform/database/migrations/2026_06_26_000417_extend_tenant_configuration_resource_identity.phpapps/platform/tests/Feature/TenantConfiguration/Spec417CanonicalIdentityPersistenceTest.phpapps/platform/tests/Feature/TenantConfiguration/Spec417CoverageResourceIdentityUpsertTest.phpapps/platform/tests/Feature/TenantConfiguration/Spec417IdentityClaimGuardFeatureTest.phpapps/platform/tests/Feature/TenantConfiguration/Spec417IdentityConflictScopeTest.phpapps/platform/tests/Feature/TenantConfiguration/Spec417IdentityNoLegacyNoUiActivationTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/ClaimGuardTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/Spec417CanonicalIdentityResolverTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/Spec417CoverageIdentityStrategyRegistryTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/Spec417CoverageSecondaryKeyBuilderTest.phpapps/platform/tests/Unit/Support/TenantConfiguration/Spec417IdentityConflictDiagnosticsTest.phpspecs/417-canonical-identity-engine/implementation-report.mdspecs/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.