package records

import (
	"context"
	"errors"
	"os"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/kernel/pkg/schema"
	metadataFeature "github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

func writeValidationProbeSchema(capabilities ...string) *models.ObjectMetadata {
	return writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeCreate, capabilities...)
}

func writeValidationProbeSchemaForPhase(phase contract.RecordWriteValidationPhase, capabilities ...string) *models.ObjectMetadata {
	schema := &models.ObjectMetadata{
		ID:           "obj_write_validation_probe",
		APIName:      "WriteValidationProbe__c",
		Label:        "Write Validation Probe",
		SharingModel: constants.SharingModelPublicReadWrite,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, Createable: false, Updateable: false},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, Createable: false, Updateable: false},
		},
	}
	if len(capabilities) > 0 {
		schema.AccessPolicy = &models.ObjectAccessPolicy{
			WriteValidation: &models.RecordWriteValidationAccessPolicy{},
		}
		for _, capability := range capabilities {
			schema.AccessPolicy.WriteValidation.Validators = append(schema.AccessPolicy.WriteValidation.Validators, models.RecordWriteValidationPolicy{
				Phase:      string(phase),
				Capability: capability,
			})
		}
	}
	makeFieldsMap(schema)
	return schema
}

func groupMemberWriteValidationEntrypointSchema(accessPolicy *models.ObjectAccessPolicy) *models.ObjectMetadata {
	schema := &models.ObjectMetadata{
		ID:           "obj_group_member",
		APIName:      constants.TableGroupMember,
		Label:        "Group Member",
		SharingModel: constants.SharingModelPublicReadWrite,
		AccessPolicy: accessPolicy,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, Createable: false, Updateable: false},
			{APIName: constants.FieldGroupMember_GroupID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldGroupMember_UserOrGroupID, Type: constants.FieldTypeString, Createable: true, Updateable: true},
			{APIName: constants.FieldIsDeleted, Type: constants.FieldTypeBoolean, Createable: false, Updateable: false},
		},
	}
	makeFieldsMap(schema)
	return schema
}

func groupMemberWriteValidationAccessPolicy() *models.ObjectAccessPolicy {
	return &models.ObjectAccessPolicy{
		WriteValidation: &models.RecordWriteValidationAccessPolicy{
			Validators: []models.RecordWriteValidationPolicy{
				{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: securityWriteValidateRoleBackedGroupMemberCapability},
				{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: securityWriteValidateRoleBackedGroupMemberCapability},
				{Phase: string(contract.RecordWriteValidationBeforeDelete), Capability: securityWriteValidateRoleBackedGroupMemberCapability},
			},
		},
	}
}

func groupUserRoleWriteValidationAccessPolicy() *models.ObjectAccessPolicy {
	return &models.ObjectAccessPolicy{
		WriteValidation: &models.RecordWriteValidationAccessPolicy{
			Validators: []models.RecordWriteValidationPolicy{
				{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: securityWriteValidateGroupUserRoleReferenceCapability},
				{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: securityWriteValidateGroupUserRoleReferenceCapability},
			},
		},
	}
}

// systemProfileOwnedImmutableWriteValidationPolicies returns the five
// mutation-phase declarations every system-profile-owned permission-resource
// table carries in managed metadata. Test schemas that write one of those
// tables must declare this capability so the fail-closed gate (which reads the
// real system_tables.json required-set) is satisfied and the seam validator
// runs instead of hard-erroring on missing metadata.
func systemProfileOwnedImmutableWriteValidationPolicies() []models.RecordWriteValidationPolicy {
	return []models.RecordWriteValidationPolicy{
		{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: securityWriteValidateSystemProfileOwnedImmutableCapability},
		{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: securityWriteValidateSystemProfileOwnedImmutableCapability},
		{Phase: string(contract.RecordWriteValidationBeforeDelete), Capability: securityWriteValidateSystemProfileOwnedImmutableCapability},
		{Phase: string(contract.RecordWriteValidationBeforeRestore), Capability: securityWriteValidateSystemProfileOwnedImmutableCapability},
		{Phase: string(contract.RecordWriteValidationBeforePurge), Capability: securityWriteValidateSystemProfileOwnedImmutableCapability},
	}
}

func managedPermissionSetDefinitionWriteValidationPolicies() []models.RecordWriteValidationPolicy {
	return []models.RecordWriteValidationPolicy{
		{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforeDelete), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforeRestore), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforePurge), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
	}
}

func managedPermissionSetDefinitionNegativeEdgeWriteValidationPolicies() []models.RecordWriteValidationPolicy {
	return []models.RecordWriteValidationPolicy{
		{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforeUpdate), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
		{Phase: string(contract.RecordWriteValidationBeforeRestore), Capability: securityWriteValidateManagedPermissionSetDefinitionCapability},
	}
}

// systemProfileOwnedImmutableOwnerResolutionSpec mirrors the ownerResolution
// declaration the four single-hop ProfileId→Profile.IsSystem tables carry in
// managed metadata. Test schemas for migrated tables declare this so the seam
// routes through the metadata-driven resolver instead of the legacy switch.
func systemProfileOwnedImmutableOwnerResolutionSpec() *models.OwnerResolutionSpec {
	return &models.OwnerResolutionSpec{
		Paths: []models.OwnerResolutionPath{{
			Hops: []models.OwnerResolutionHop{{
				// "ProfileId" is the shared FK field API name on all four
				// single-hop tables (ProfilePermission, ProfileCustomPermission,
				// ProfileTabSetting, ProfileAppVisibility).
				ViaField: "ProfileId",
				ToObject: constants.TableProfile,
				SystemWhen: &models.OwnerPredicate{
					Field: constants.FieldProfile_IsSystem,
					Is:    true,
				},
			}},
		}},
	}
}

func TestRecordWriteValidatorRegistrationRunsBeforeCreateSQL(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(writeValidationProbeSchema("test.write.validate"), repo)
	sentinel := errors.New("blocked by write validator")
	var called bool
	var eventRecord contract.Record

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			called = true
			if repo.inserted != nil {
				t.Fatal("write validator ran after repo.Insert; want pre-SQL validation")
			}
			if event.ObjectName != "WriteValidationProbe__c" {
				t.Fatalf("ObjectName = %q, want WriteValidationProbe__c", event.ObjectName)
			}
			if event.RecordID == "" {
				t.Fatalf("RecordID is empty; want generated Id visible to validator")
			}
			if len(event.OldRecord) != 0 {
				t.Fatalf("OldRecord = %#v, want empty for create", event.OldRecord)
			}
			eventRecord = event.Record
			event.Record[constants.FieldName] = "mutated"
			return sentinel
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	_, err := ps.Insert(context.Background(), "WriteValidationProbe__c", models.SObject{
		constants.FieldName: "write-validator",
	}, recordEntrypointUser())
	if !errors.Is(err, sentinel) {
		t.Fatalf("Insert error = %v, want sentinel validator error", err)
	}
	if !called {
		t.Fatal("write validator was not called")
	}
	if repo.inserted != nil {
		t.Fatalf("repo.Insert ran after failing write validator: %#v", repo.inserted)
	}
	if eventRecord == nil || eventRecord[constants.FieldName] != "mutated" {
		t.Fatalf("test did not mutate event copy as expected: %#v", eventRecord)
	}
}

func TestRecordWriteValidatorRegistrationDoesNotMutateCreateRecord(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(writeValidationProbeSchema("test.write.normalize"), repo)

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.normalize",
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			event.Record[constants.FieldName] = "normalized"
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	_, err := ps.Insert(context.Background(), "WriteValidationProbe__c", models.SObject{
		constants.FieldName: "raw",
	}, recordEntrypointUser())
	if err != nil {
		t.Fatalf("Insert returned error: %v", err)
	}
	if repo.inserted[constants.FieldName] != "raw" {
		t.Fatalf("inserted Name = %v, want raw; record=%#v", repo.inserted[constants.FieldName], repo.inserted)
	}
}

func TestRecordWriteValidatorFailsClosedWhenMetadataCapabilityHasNoHandler(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(writeValidationProbeSchema("test.write.missing"), repo)

	_, err := ps.Insert(context.Background(), "WriteValidationProbe__c", models.SObject{
		constants.FieldName: "missing-handler",
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Insert succeeded, want missing write-validator capability error")
	}
	if !strings.Contains(err.Error(), "missing record write validator") ||
		!strings.Contains(err.Error(), "test.write.missing") {
		t.Fatalf("Insert error = %v, want missing write-validator capability", err)
	}
	if repo.inserted != nil {
		t.Fatalf("repo.Insert ran after missing write validator: %#v", repo.inserted)
	}
}

func TestSecurityWriteValidationMetadataRequiredUsesManagedDeclarations(t *testing.T) {
	definitions := []schema.TableDefinition{
		{
			TableName: "RegisteredWriteValidationObject",
			AccessPolicy: &models.ObjectAccessPolicy{
				WriteValidation: &models.RecordWriteValidationAccessPolicy{
					Validators: []models.RecordWriteValidationPolicy{{
						Phase:      string(contract.RecordWriteValidationBeforeCreate),
						Capability: "toy.write.validate",
					}},
				},
			},
		},
		{
			TableName: "UnregisteredObject",
		},
	}

	required := securityWriteValidationMetadataRequiredObjectsFromDefinitions(definitions)
	createKey, ok := securityWriteValidationMetadataRequirementKey("registeredwritevalidationobject", contract.RecordWriteValidationBeforeCreate, "toy.write.validate")
	if !ok {
		t.Fatal("create requirement key was not constructed")
	}
	if _, ok := required[createKey]; !ok {
		t.Fatal("metadata requirement was not derived from writeValidation declaration")
	}
	deleteKey, ok := securityWriteValidationMetadataRequirementKey("registeredwritevalidationobject", contract.RecordWriteValidationBeforeDelete, "toy.write.validate")
	if !ok {
		t.Fatal("delete requirement key was not constructed")
	}
	if _, ok := required[deleteKey]; ok {
		t.Fatal("metadata requirement was derived for undeclared writeValidation phase")
	}
	unregisteredKey, ok := securityWriteValidationMetadataRequirementKey("UnregisteredObject", contract.RecordWriteValidationBeforeCreate, "toy.write.validate")
	if !ok {
		t.Fatal("unregistered requirement key was not constructed")
	}
	if _, ok := required[unregisteredKey]; ok {
		t.Fatal("metadata requirement was derived for object without writeValidation declaration")
	}
}

func TestRecordWriteValidationFailsClosedWhenRequiredValidationRuleMissing(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.AccessPolicy = &models.ObjectAccessPolicy{
		WriteValidation: &models.RecordWriteValidationAccessPolicy{
			RequiredValidationRules: []models.RecordWriteValidationRuleRequirement{{
				Phase:     string(contract.RecordWriteValidationBeforeCreate),
				Name:      "Probe_Required_Rule",
				OwnerPack: "probe-pack",
			}},
		},
	}
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	ps.metadata = metadataFeature.NewMetadataService(&stubUpsertMetaRepo{
		schema: schema,
	}, nil, nil)

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: schema.APIName,
		Record: models.SObject{
			constants.FieldID: "probe-record",
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want missing required ValidationRule metadata error")
	}
	if !strings.Contains(err.Error(), "missing required validation rule metadata") ||
		!strings.Contains(err.Error(), "Probe_Required_Rule") {
		t.Fatalf("runRecordWriteValidators error = %v, want missing required ValidationRule metadata", err)
	}
}

// TestSecurityWriteValidationMetadataRequiredRecognizesCapabilityGranularity
// proves the required-set tracks capabilities individually: a (table, phase) that
// declares two capabilities produces two separate entries, and dropping one
// capability from the definition removes only that entry while the survivor stays.
// This is the builder-level guarantee that the fail-closed gate at
// capability granularity can detect partial metadata drops.
func TestSecurityWriteValidationMetadataRequiredRecognizesCapabilityGranularity(t *testing.T) {
	definitions := []schema.TableDefinition{
		{
			TableName: "MultiCapTable",
			AccessPolicy: &models.ObjectAccessPolicy{
				WriteValidation: &models.RecordWriteValidationAccessPolicy{
					Validators: []models.RecordWriteValidationPolicy{
						{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: "toy.write.alpha"},
						{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: "toy.write.beta"},
					},
				},
			},
		},
	}
	required := securityWriteValidationMetadataRequiredObjectsFromDefinitions(definitions)

	// Both capabilities are required.
	keyA, ok := securityWriteValidationMetadataRequirementKey("MultiCapTable", contract.RecordWriteValidationBeforeCreate, "toy.write.alpha")
	if !ok {
		t.Fatal("key for toy.write.alpha was not constructed")
	}
	if _, found := required[keyA]; !found {
		t.Fatal("required-set missing toy.write.alpha when both capabilities are declared")
	}
	keyB, ok := securityWriteValidationMetadataRequirementKey("MultiCapTable", contract.RecordWriteValidationBeforeCreate, "toy.write.beta")
	if !ok {
		t.Fatal("key for toy.write.beta was not constructed")
	}
	if _, found := required[keyB]; !found {
		t.Fatal("required-set missing toy.write.beta when both capabilities are declared")
	}

	// Drop alpha from the definition: only beta remains required.
	single := []schema.TableDefinition{
		{
			TableName: "MultiCapTable",
			AccessPolicy: &models.ObjectAccessPolicy{
				WriteValidation: &models.RecordWriteValidationAccessPolicy{
					Validators: []models.RecordWriteValidationPolicy{
						{Phase: string(contract.RecordWriteValidationBeforeCreate), Capability: "toy.write.beta"},
					},
				},
			},
		},
	}
	reqSingle := securityWriteValidationMetadataRequiredObjectsFromDefinitions(single)
	if _, found := reqSingle[keyA]; found {
		t.Fatal("required-set still contains toy.write.alpha after it was dropped from the declaration")
	}
	if _, found := reqSingle[keyB]; !found {
		t.Fatal("required-set missing toy.write.beta after alpha was dropped but beta kept")
	}
}

func TestSecurityWriteValidationFailsClosedWhenManagedMetadataOmitsPolicy(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TableObjectPermissions
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableObjectPermissions,
		Record: models.SObject{
			constants.FieldID: "110-test",
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want missing security write-validation metadata error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), constants.TableObjectPermissions) {
		t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, constants.TableObjectPermissions)
	}
}

func TestObjectPermissionsInsertFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := objectPermissionsEntrypointSchema()
	schema.AccessPolicy = nil
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(schema, repo)

	_, err := ps.Insert(context.Background(), constants.TableObjectPermissions, models.SObject{
		constants.FieldObjectPermissions_ParentID:                    "0PS000000000001",
		constants.FieldObjectPermissions_SobjectType:                 "Account",
		constants.FieldObjectPermissions_PermissionsRead:             false,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Insert succeeded after effective ObjectPermissions metadata dropped writeValidation, want fail-closed error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), constants.TableObjectPermissions) {
		t.Fatalf("Insert error = %v, want missing metadata for %s", err, constants.TableObjectPermissions)
	}
	if repo.inserted != nil {
		t.Fatalf("repo.Insert ran after missing ObjectPermissions writeValidation metadata: %#v", repo.inserted)
	}
}

func TestObjectPermissionsUpdateFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := objectPermissionsEntrypointSchema()
	schema.AccessPolicy = nil
	repo := &captureRecordRepo{old: models.SObject{
		constants.FieldID:                                  "op-row",
		constants.FieldObjectPermissions_ParentID:          "0PS000000000001",
		constants.FieldObjectPermissions_SobjectType:       "Account",
		constants.FieldObjectPermissions_PermissionsRead:   true,
		constants.FieldObjectPermissions_PermissionsCreate: false,
	}}
	ps := newRecordEntrypointTestService(schema, repo)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "ObjectPermissions write-validation metadata test")

	err := ps.Update(ctx, constants.TableObjectPermissions, "op-row", models.SObject{
		constants.FieldObjectPermissions_PermissionsRead:             false,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
	}, recordEntrypointUser())
	if err == nil {
		t.Fatal("Update succeeded after effective ObjectPermissions metadata dropped writeValidation, want fail-closed error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), constants.TableObjectPermissions) {
		t.Fatalf("Update error = %v, want missing metadata for %s", err, constants.TableObjectPermissions)
	}
	if repo.updated != nil {
		t.Fatalf("repo.Update ran after missing ObjectPermissions writeValidation metadata: %#v", repo.updated)
	}
}

func TestObjectPermissionsPreviewUpdateFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := objectPermissionsEntrypointSchema()
	schema.AccessPolicy = nil
	repo := &captureRecordRepo{old: models.SObject{
		constants.FieldID:                                  "op-row",
		constants.FieldObjectPermissions_ParentID:          "0PS000000000001",
		constants.FieldObjectPermissions_SobjectType:       "Account",
		constants.FieldObjectPermissions_PermissionsRead:   true,
		constants.FieldObjectPermissions_PermissionsCreate: false,
	}}
	ps := newRecordEntrypointTestService(schema, repo)
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "ObjectPermissions write-validation metadata test")

	_, err := ps.PreviewBeforeUpdate(ctx, constants.TableObjectPermissions, "op-row", models.SObject{
		constants.FieldObjectPermissions_PermissionsRead:             false,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
	}, nil, recordEntrypointUser())
	if err == nil {
		t.Fatal("PreviewBeforeUpdate succeeded after effective ObjectPermissions metadata dropped writeValidation, want fail-closed error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), constants.TableObjectPermissions) {
		t.Fatalf("PreviewBeforeUpdate error = %v, want missing metadata for %s", err, constants.TableObjectPermissions)
	}
}

func TestObjectPermissionsBulkInsertFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := objectPermissionsEntrypointSchema()
	schema.AccessPolicy = nil
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(schema, repo)

	result, err := ps.BulkInsert(context.Background(), constants.TableObjectPermissions, []models.SObject{{
		constants.FieldObjectPermissions_ParentID:                    "0PS000000000001",
		constants.FieldObjectPermissions_SobjectType:                 "Account",
		constants.FieldObjectPermissions_PermissionsRead:             false,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
	}}, recordEntrypointUser(), BulkInsertOptions{})
	if err == nil {
		t.Fatal("BulkInsert returned nil top-level error after all records failed write validation")
	}
	if !strings.Contains(err.Error(), "all 1 records failed validation") {
		t.Fatalf("BulkInsert top-level error = %v, want all-records-failed validation wrapper", err)
	}
	if result.SuccessCount != 0 || result.FailedCount != 1 {
		t.Fatalf("BulkInsert result = %+v, want one fail-closed metadata error", result)
	}
	if len(result.Errors) != 1 ||
		!strings.Contains(result.Errors[0], "missing record write validation metadata") ||
		!strings.Contains(result.Errors[0], constants.TableObjectPermissions) {
		t.Fatalf("BulkInsert errors = %+v, want missing metadata for %s", result.Errors, constants.TableObjectPermissions)
	}
	if len(repo.bulkInserted) != 0 {
		t.Fatalf("repo.BulkInsert ran after missing ObjectPermissions writeValidation metadata: %#v", repo.bulkInserted)
	}
}

func TestPersistencePathsDoNotCallObjectSecurityValidationHelpers(t *testing.T) {
	files := []string{
		"persistence_insert.go",
		"persistence_bulk_insert.go",
		"persistence_service.go",
		"persistence_recyclebin_restore.go",
	}
	disallowed := []string{
		"validateObjectSecurityContract(",
		"validatePreFlowObjectSecurityContract(",
	}

	for _, file := range files {
		src, err := os.ReadFile(file)
		if err != nil {
			t.Fatalf("read %s: %v", file, err)
		}
		for _, helper := range disallowed {
			if strings.Contains(string(src), helper) {
				t.Errorf("%s calls %s directly; use registered record write validators", file, strings.TrimSuffix(helper, "("))
			}
		}
	}
}

func TestPersistencePathsDoNotCallObjectSecurityCanonicalizationHelpers(t *testing.T) {
	files := []string{
		"persistence_insert.go",
		"persistence_bulk_insert.go",
		"persistence_service.go",
		"persistence_recyclebin_restore.go",
	}
	disallowed := []string{
		"normalizeObjectSecurityContract(",
		"materializeObjectSecurityContractClears(",
	}

	for _, file := range files {
		src, err := os.ReadFile(file)
		if err != nil {
			t.Fatalf("read %s: %v", file, err)
		}
		for _, helper := range disallowed {
			if strings.Contains(string(src), helper) {
				t.Errorf("%s calls %s directly; use registered record write canonicalizers", file, strings.TrimSuffix(helper, "("))
			}
		}
	}
}

func TestPermissionSetAssignmentWriteValidationFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TablePermissionSetAssignment
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	tests := []struct {
		name     string
		phase    recordWriteValidationPhase
		recordID string
	}{
		{name: "before create", phase: recordWriteValidationBeforeCreate},
		{name: "before update", phase: recordWriteValidationBeforeUpdate, recordID: "0Pa-test"},
		{name: "before delete", phase: recordWriteValidationBeforeDelete, recordID: "0Pa-test"},
		{name: "before restore", phase: recordWriteValidationBeforeRestore, recordID: "0Pa-test"},
		{name: "before purge", phase: recordWriteValidationBeforePurge, recordID: "0Pa-test"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ps.runRecordWriteValidators(context.Background(), tt.phase, recordWriteValidationEvent{
				ObjectName: constants.TablePermissionSetAssignment,
				RecordID:   tt.recordID,
				Record: models.SObject{
					constants.FieldID: "0Pa-test",
				},
				OldRecord: models.SObject{
					constants.FieldID: "0Pa-test",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing PermissionSetAssignment write-validation metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") ||
				!strings.Contains(err.Error(), constants.TablePermissionSetAssignment) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, constants.TablePermissionSetAssignment)
			}
		})
	}
}

func TestSharingRuleWriteValidationFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TableSharingRule
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	tests := []struct {
		name     string
		phase    recordWriteValidationPhase
		recordID string
	}{
		{name: "before create", phase: recordWriteValidationBeforeCreate},
		{name: "before update", phase: recordWriteValidationBeforeUpdate, recordID: "01q-test"},
		{name: "before restore", phase: recordWriteValidationBeforeRestore, recordID: "01q-test"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ps.runRecordWriteValidators(context.Background(), tt.phase, recordWriteValidationEvent{
				ObjectName: constants.TableSharingRule,
				RecordID:   tt.recordID,
				Record: models.SObject{
					constants.FieldID: "01q-test",
				},
				OldRecord: models.SObject{
					constants.FieldID: "01q-test",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing SharingRule write-validation metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") ||
				!strings.Contains(err.Error(), constants.TableSharingRule) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, constants.TableSharingRule)
			}
		})
	}
}

func TestSharingRuleWriteCanonicalizationFailsClosedWhenEffectiveMetadataDropsCanonicalizer(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TableSharingRule
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	tests := []struct {
		name  string
		phase recordWriteValidationPhase
	}{
		{name: "before create", phase: recordWriteValidationBeforeCreate},
		{name: "before update", phase: recordWriteValidationBeforeUpdate},
		{name: "before restore", phase: recordWriteValidationBeforeRestore},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ps.runRecordWriteCanonicalizers(context.Background(), tt.phase, recordWriteCanonicalizationEvent{
				ObjectName: constants.TableSharingRule,
				RecordID:   "01q-test",
				Record: models.SObject{
					constants.FieldID: "01q-test",
				},
				OldRecord: models.SObject{
					constants.FieldID: "01q-test",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteCanonicalizers succeeded, want missing SharingRule write-canonicalization metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write canonicalization metadata") ||
				!strings.Contains(err.Error(), constants.TableSharingRule) ||
				!strings.Contains(err.Error(), "platform.security.write.sharing-rule-canonicalization") {
				t.Fatalf("runRecordWriteCanonicalizers error = %v, want missing canonicalization metadata for %s", err, constants.TableSharingRule)
			}
		})
	}
}

func TestUserRoleWriteValidationFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TableUserRole
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	tests := []struct {
		name     string
		phase    recordWriteValidationPhase
		recordID string
	}{
		{name: "before create", phase: recordWriteValidationBeforeCreate},
		{name: "before update", phase: recordWriteValidationBeforeUpdate, recordID: "00E-child"},
		{name: "before delete", phase: recordWriteValidationBeforeDelete, recordID: "00E-child"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := ps.runRecordWriteValidators(context.Background(), tt.phase, recordWriteValidationEvent{
				ObjectName: constants.TableUserRole,
				RecordID:   tt.recordID,
				Record: models.SObject{
					constants.FieldID:                    "00E-child",
					constants.FieldUserRole_ParentRoleID: "00E-parent",
				},
				OldRecord: models.SObject{
					constants.FieldID: "00E-child",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing UserRole write-validation metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") ||
				!strings.Contains(err.Error(), constants.TableUserRole) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, constants.TableUserRole)
			}
		})
	}
}

func TestSecurityWriteValidationDoesNotRequireUndeclaredDeletePhase(t *testing.T) {
	schema := writeValidationProbeSchema()
	schema.APIName = constants.TableSharingRule
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeDelete, recordWriteValidationEvent{
		ObjectName: constants.TableSharingRule,
		RecordID:   "sharing-rule-test",
		Record: models.SObject{
			constants.FieldID: "sharing-rule-test",
		},
		OldRecord: models.SObject{
			constants.FieldID: "sharing-rule-test",
		},
	})
	if err != nil {
		t.Fatalf("runRecordWriteValidators before_delete error = %v, want nil when managed metadata declares no delete validator", err)
	}
}

func TestSharingRuleWriteValidationFailsClosedWhenCapabilityHasNoHandler(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		sharingRuleOwnerReferenceWriteValidationCapabilityForTest,
		sharingRuleShapeWriteValidationCapabilityForTest,
	)
	schema.APIName = constants.TableSharingRule
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	ps.writeValidators = newRecordWriteValidators()

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableSharingRule,
		Record: models.SObject{
			constants.FieldID: "01q-test",
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want missing SharingRule validator handler error")
	}
	if !strings.Contains(err.Error(), "missing record write validator") ||
		!strings.Contains(err.Error(), sharingRuleOwnerReferenceWriteValidationCapabilityForTest) {
		t.Fatalf("runRecordWriteValidators error = %v, want missing handler for %q", err, sharingRuleOwnerReferenceWriteValidationCapabilityForTest)
	}
}

func TestPermissionSetAssignmentWriteValidationFailsClosedWhenCapabilityHasNoHandler(t *testing.T) {
	const capability = "platform.security.write.permission-set-assignment-target"
	schema := writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeCreate, capability)
	schema.APIName = constants.TablePermissionSetAssignment
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	ps.writeValidators = newRecordWriteValidators()

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TablePermissionSetAssignment,
		Record: models.SObject{
			constants.FieldPermissionSetAssignment_PermissionSetID: "ps-custom",
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want missing PermissionSetAssignment validator handler error")
	}
	if !strings.Contains(err.Error(), "missing record write validator") ||
		!strings.Contains(err.Error(), capability) {
		t.Fatalf("runRecordWriteValidators error = %v, want missing handler for %q", err, capability)
	}
}

func TestGroupMemberRoleBackedWriteValidationFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	tests := []struct {
		name       string
		objectName string
		phase      recordWriteValidationPhase
	}{
		{name: "group member before create", objectName: constants.TableGroupMember, phase: recordWriteValidationBeforeCreate},
		{name: "group member before update", objectName: constants.TableGroupMember, phase: recordWriteValidationBeforeUpdate},
		{name: "group member before delete", objectName: constants.TableGroupMember, phase: recordWriteValidationBeforeDelete},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchema()
			schema.APIName = tt.objectName
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

			err := ps.runRecordWriteValidators(context.Background(), tt.phase, recordWriteValidationEvent{
				ObjectName: tt.objectName,
				RecordID:   "security-row",
				Record: models.SObject{
					constants.FieldID: "security-row",
				},
				OldRecord: models.SObject{
					constants.FieldID: "security-row",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing security write-validation metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") ||
				!strings.Contains(err.Error(), tt.objectName) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, tt.objectName)
			}
		})
	}
}

func TestGroupUserRoleWriteValidationFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	tests := []struct {
		name  string
		phase recordWriteValidationPhase
	}{
		{name: "before create", phase: recordWriteValidationBeforeCreate},
		{name: "before update", phase: recordWriteValidationBeforeUpdate},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchema()
			schema.APIName = constants.TableGroup
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

			err := ps.runRecordWriteValidators(context.Background(), tt.phase, recordWriteValidationEvent{
				ObjectName: constants.TableGroup,
				RecordID:   "00G-group",
				Record: models.SObject{
					constants.FieldID:         "00G-group",
					constants.FieldGroup_Type: "Queue",
				},
				OldRecord: models.SObject{
					constants.FieldID:         "00G-group",
					constants.FieldGroup_Type: "Queue",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing Group write-validation metadata error")
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") ||
				!strings.Contains(err.Error(), constants.TableGroup) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing metadata for %s", err, constants.TableGroup)
			}
		})
	}
}

func TestGroupMemberRoleBackedWriteValidationFailsClosedWhenCapabilityHasNoHandler(t *testing.T) {
	tests := []struct {
		name       string
		objectName string
		phase      contract.RecordWriteValidationPhase
		internal   recordWriteValidationPhase
		capability string
	}{
		{name: "group member before create", objectName: constants.TableGroupMember, phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate, capability: securityWriteValidateRoleBackedGroupMemberCapability},
		{name: "group member before update", objectName: constants.TableGroupMember, phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate, capability: securityWriteValidateRoleBackedGroupMemberCapability},
		{name: "group member before delete", objectName: constants.TableGroupMember, phase: contract.RecordWriteValidationBeforeDelete, internal: recordWriteValidationBeforeDelete, capability: securityWriteValidateRoleBackedGroupMemberCapability},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, tt.capability)
			schema.APIName = tt.objectName
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
			ps.writeValidators = newRecordWriteValidators()

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: tt.objectName,
				RecordID:   "security-row",
				Record: models.SObject{
					constants.FieldID: "security-row",
				},
				OldRecord: models.SObject{
					constants.FieldID: "security-row",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing validator handler error")
			}
			if !strings.Contains(err.Error(), "missing record write validator") ||
				!strings.Contains(err.Error(), tt.capability) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing handler for %q", err, tt.capability)
			}
		})
	}
}

func TestGroupUserRoleWriteValidationFailsClosedWhenCapabilityHasNoHandler(t *testing.T) {
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before create", phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate},
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, securityWriteValidateGroupUserRoleReferenceCapability)
			schema.APIName = constants.TableGroup
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
			ps.writeValidators = newRecordWriteValidators()

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableGroup,
				RecordID:   "00G-group",
				Record: models.SObject{
					constants.FieldID:         "00G-group",
					constants.FieldGroup_Type: "Queue",
				},
				OldRecord: models.SObject{
					constants.FieldID:         "00G-group",
					constants.FieldGroup_Type: "Queue",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing validator handler error")
			}
			if !strings.Contains(err.Error(), "missing record write validator") ||
				!strings.Contains(err.Error(), securityWriteValidateGroupUserRoleReferenceCapability) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing handler for %q", err, securityWriteValidateGroupUserRoleReferenceCapability)
			}
		})
	}
}

func TestBuiltInGroupMemberRoleBackedWriteValidatorsUseMetadataCapabilities(t *testing.T) {
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before create", phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate},
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
		{name: "before delete", phase: contract.RecordWriteValidationBeforeDelete, internal: recordWriteValidationBeforeDelete},
	}

	for _, tt := range tests {
		t.Run(tt.name+" rejects role-backed group target", func(t *testing.T) {
			schema := groupMemberWriteValidationEntrypointSchema(&models.ObjectAccessPolicy{
				WriteValidation: &models.RecordWriteValidationAccessPolicy{
					Validators: []models.RecordWriteValidationPolicy{{
						Phase:      string(tt.phase),
						Capability: securityWriteValidateRoleBackedGroupMemberCapability,
					}},
				},
			})
			repo := &captureRecordRepo{
				findOneFn: func(_ context.Context, tableName string, id string) (models.SObject, error) {
					if tableName == constants.TableGroup && id == "00G-role" {
						return models.SObject{
							constants.FieldID:               "00G-role",
							constants.FieldGroup_Type:       "Role",
							constants.FieldGroup_UserRoleID: "00E-role",
						}, nil
					}
					return nil, nil
				},
			}
			ps := newRecordEntrypointTestService(schema, repo)

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableGroupMember,
				RecordID:   "011-member",
				Record: models.SObject{
					constants.FieldID:                  "011-member",
					constants.FieldGroupMember_GroupID: "00G-role",
				},
				OldRecord: models.SObject{
					constants.FieldID:                  "011-member",
					constants.FieldGroupMember_GroupID: "00G-role",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want role-backed GroupMember rejection")
			}
			if !strings.Contains(err.Error(), roleBackedGroupMemberMutationMessage) {
				t.Fatalf("runRecordWriteValidators error = %v, want %q", err, roleBackedGroupMemberMutationMessage)
			}
		})
	}
}

func TestBuiltInGroupUserRoleWriteValidatorUsesMetadataCapability(t *testing.T) {
	t.Run("validates blank non role-backed UserRoleId clears without mutating event record", func(t *testing.T) {
		schema := writeValidationProbeSchemaForPhase(
			contract.RecordWriteValidationBeforeUpdate,
			securityWriteValidateGroupUserRoleReferenceCapability,
		)
		schema.APIName = constants.TableGroup
		ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
		record := models.SObject{
			constants.FieldID:               "00G-group",
			constants.FieldGroup_Type:       "Queue",
			constants.FieldGroup_UserRoleID: "   ",
		}

		err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeUpdate, recordWriteValidationEvent{
			ObjectName: constants.TableGroup,
			RecordID:   "00G-group",
			Record:     record,
			OldRecord: models.SObject{
				constants.FieldID:               "00G-group",
				constants.FieldGroup_Type:       "Queue",
				constants.FieldGroup_UserRoleID: "00E-role",
			},
		})
		if err != nil {
			t.Fatalf("runRecordWriteValidators returned error: %v", err)
		}
		if val := record[constants.FieldGroup_UserRoleID]; val != "   " {
			t.Fatalf("record UserRoleId = %#v, want original blank string to prove validator event is copy-only", val)
		}
	})

	t.Run("rejects role-backed groups without UserRoleId", func(t *testing.T) {
		schema := writeValidationProbeSchemaForPhase(
			contract.RecordWriteValidationBeforeCreate,
			securityWriteValidateGroupUserRoleReferenceCapability,
		)
		schema.APIName = constants.TableGroup
		ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

		err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
			ObjectName: constants.TableGroup,
			RecordID:   "00G-group",
			Record: models.SObject{
				constants.FieldID:         "00G-group",
				constants.FieldGroup_Type: "Role",
			},
		})
		if err == nil {
			t.Fatal("runRecordWriteValidators succeeded, want missing UserRoleId validation error")
		}
		if !strings.Contains(err.Error(), groupUserRoleRequiredMessage) {
			t.Fatalf("runRecordWriteValidators error = %v, want %q", err, groupUserRoleRequiredMessage)
		}
	})
}

func TestGroupMemberInsertFailsClosedWhenEffectiveMetadataDropsWriteValidator(t *testing.T) {
	schema := groupMemberWriteValidationEntrypointSchema(nil)
	repo := &captureRecordRepo{
		findOneFn: func(_ context.Context, tableName string, id string) (models.SObject, error) {
			if tableName == constants.TableGroup && id == "00G-role" {
				return models.SObject{
					constants.FieldID:               "00G-role",
					constants.FieldGroup_Type:       "Role",
					constants.FieldGroup_UserRoleID: "00E-role",
				}, nil
			}
			return nil, nil
		},
	}
	ps := newRecordEntrypointTestService(schema, repo)

	ctx := context.WithValue(context.Background(), groupMembershipClosureTxStateKey{}, &groupMembershipClosureTxState{})
	err := ps.RunInTransaction(ctx, func(txCtx context.Context) error {
		_, insertErr := ps.Insert(txCtx, constants.TableGroupMember, models.SObject{
			constants.FieldGroupMember_GroupID:       "00G-role",
			constants.FieldGroupMember_UserOrGroupID: "005-user",
		}, recordEntrypointUser())
		return insertErr
	})
	if err == nil {
		t.Fatal("Insert succeeded after effective GroupMember metadata dropped writeValidation, want fail-closed error")
	}
	if !strings.Contains(err.Error(), "missing record write validation metadata") ||
		!strings.Contains(err.Error(), constants.TableGroupMember) {
		t.Fatalf("Insert error = %v, want missing metadata for %s", err, constants.TableGroupMember)
	}
	if repo.inserted != nil {
		t.Fatalf("repo.Insert ran after missing GroupMember writeValidation metadata: %#v", repo.inserted)
	}
}

func TestUserRoleWriteValidationFailsClosedWhenCapabilityHasNoHandler(t *testing.T) {
	const capability = "platform.security.write.user-role-parent"
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
		{name: "before delete", phase: contract.RecordWriteValidationBeforeDelete, internal: recordWriteValidationBeforeDelete},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, capability)
			schema.APIName = constants.TableUserRole
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
			ps.writeValidators = newRecordWriteValidators()

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableUserRole,
				RecordID:   "00E-child",
				Record: models.SObject{
					constants.FieldID:                    "00E-child",
					constants.FieldUserRole_ParentRoleID: "00E-parent",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing UserRole validator handler error")
			}
			if !strings.Contains(err.Error(), "missing record write validator") ||
				!strings.Contains(err.Error(), capability) {
				t.Fatalf("runRecordWriteValidators error = %v, want missing handler for %q", err, capability)
			}
		})
	}
}

func TestRecordWriteValidatorBulkInsertFiltersRejectedRowsBeforeSQL(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(writeValidationProbeSchema("test.write.validate"), repo)
	var calls int

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			calls++
			if repo.bulkInserted != nil {
				t.Fatal("write validator ran after repo.BulkInsert; want pre-SQL validation")
			}
			if event.RecordID == "" {
				t.Fatalf("RecordID is empty for bulk create event: %#v", event)
			}
			if event.Record[constants.FieldName] == "blocked" {
				return errors.New("blocked by bulk validator")
			}
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	result, err := ps.BulkInsert(context.Background(), "WriteValidationProbe__c", []models.SObject{
		{constants.FieldName: "blocked"},
		{constants.FieldName: "allowed"},
	}, recordEntrypointUser(), BulkInsertOptions{})
	if err != nil {
		t.Fatalf("BulkInsert returned error: %v", err)
	}
	if calls != 2 {
		t.Fatalf("validator calls = %d, want 2", calls)
	}
	if result.SuccessCount != 1 || result.FailedCount != 1 {
		t.Fatalf("BulkInsert result = %+v, want one success and one failure", result)
	}
	if len(repo.bulkInserted) != 1 {
		t.Fatalf("repo.BulkInsert rows = %d, want 1", len(repo.bulkInserted))
	}
	if repo.bulkInserted[0][constants.FieldName] != "allowed" {
		t.Fatalf("bulk inserted row = %#v, want allowed row only", repo.bulkInserted[0])
	}
}

func TestRecordWriteValidatorBulkInsertReceivesPendingCreatesBeforeSQL(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(writeValidationProbeSchema("test.write.batch"), repo)
	var calls int

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.batch",
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			calls++
			if repo.bulkInserted != nil {
				t.Fatal("write validator ran after repo.BulkInsert; want pending creates visible before SQL")
			}
			if event.RecordID == "" {
				t.Fatalf("RecordID is empty for bulk create event: %#v", event)
			}
			if len(event.PendingCreates) != 2 {
				t.Fatalf("PendingCreates = %#v, want both same-batch create rows", event.PendingCreates)
			}
			pendingNames := make(map[string]string, len(event.PendingCreates))
			for _, pending := range event.PendingCreates {
				if pending.RecordID == "" {
					t.Fatalf("pending create has empty RecordID: %#v", event.PendingCreates)
				}
				if pending.Record[constants.FieldID] != pending.RecordID {
					t.Fatalf("pending create RecordID = %q, record Id = %v", pending.RecordID, pending.Record[constants.FieldID])
				}
				pendingNames[pending.RecordID] = stringField(contractRecordToSObject(pending.Record), constants.FieldName)
			}
			seen := map[string]bool{}
			for _, name := range pendingNames {
				seen[name] = true
			}
			if !seen["alpha"] || !seen["bravo"] {
				t.Fatalf("PendingCreates = %#v, want both final same-batch records", event.PendingCreates)
			}
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	result, err := ps.BulkInsert(context.Background(), "WriteValidationProbe__c", []models.SObject{
		{constants.FieldID: "probe-a", constants.FieldName: "alpha"},
		{constants.FieldID: "probe-b", constants.FieldName: "bravo"},
	}, recordEntrypointUser(), BulkInsertOptions{AllowSystemFields: true})
	if err != nil {
		t.Fatalf("BulkInsert returned error: %v", err)
	}
	if calls != 2 {
		t.Fatalf("validator calls = %d, want 2", calls)
	}
	if result.SuccessCount != 2 || result.FailedCount != 0 {
		t.Fatalf("BulkInsert result = %+v, want both records inserted", result)
	}
	if len(repo.bulkInserted) != 2 {
		t.Fatalf("repo.BulkInsert rows = %d, want 2", len(repo.bulkInserted))
	}
}

func TestBuiltInSharingRuleOwnerReferenceWriteValidatorUsesMetadataCapability(t *testing.T) {
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before create", phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate},
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
		{name: "before restore", phase: contract.RecordWriteValidationBeforeRestore, internal: recordWriteValidationBeforeRestore},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(
				tt.phase,
				sharingRuleOwnerReferenceWriteValidationCapabilityForTest,
				sharingRuleShapeWriteValidationCapabilityForTest,
			)
			schema.APIName = constants.TableSharingRule
			repo := &sharingRuleEntrypointRepo{
				findOneFn: func(_ context.Context, tableName string, id string) (models.SObject, error) {
					if tableName == constants.TableGroup && id == "group-existing-target" {
						return models.SObject{constants.FieldID: id}, nil
					}
					return nil, nil
				},
			}
			ps := newSharingRuleEntrypointTestService(schema, repo, mutatingRecordEventBus{})

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableSharingRule,
				RecordID:   "01q-test",
				Record: models.SObject{
					constants.FieldID: "01q-test",
					constants.FieldSharingRule_SharedFromGroupID: "group-missing-target",
					constants.FieldSharingRule_ShareWithGroupID:  "group-existing-target",
				},
				OldRecord: models.SObject{
					constants.FieldID: "01q-test",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want missing SharingRule owner reference rejection")
			}
			if !strings.Contains(err.Error(), constants.FieldSharingRule_SharedFromGroupID) ||
				!strings.Contains(err.Error(), constants.TableGroup) {
				t.Fatalf("runRecordWriteValidators error = %v, want SharingRule owner reference validation", err)
			}
		})
	}
}

func TestBuiltInSharingRuleShapeWriteValidatorUsesMetadataCapability(t *testing.T) {
	const shapeCapability = "platform.security.write.sharing-rule-shape"
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before create", phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate},
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
		{name: "before restore", phase: contract.RecordWriteValidationBeforeRestore, internal: recordWriteValidationBeforeRestore},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := sharingRuleEntrypointSchema()
			schema.AccessPolicy.WriteValidation.Validators = []models.RecordWriteValidationPolicy{
				{Phase: string(tt.phase), Capability: sharingRuleOwnerReferenceWriteValidationCapabilityForTest},
				{Phase: string(tt.phase), Capability: shapeCapability},
			}
			repo := &sharingRuleEntrypointRepo{
				findOneFn: func(_ context.Context, tableName string, id string) (models.SObject, error) {
					if tableName == constants.TableGroup && id == "group-existing-source" {
						return models.SObject{constants.FieldID: id}, nil
					}
					return nil, nil
				},
			}
			ps := newSharingRuleEntrypointTestService(schema, repo, mutatingRecordEventBus{})

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableSharingRule,
				RecordID:   "01q-test",
				Record: models.SObject{
					constants.FieldID: "01q-test",
					constants.FieldSharingRule_SharedFromGroupID: "group-existing-source",
				},
				OldRecord: models.SObject{
					constants.FieldID: "01q-test",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want SharingRule shape validation error")
			}
			if !strings.Contains(err.Error(), "ShareWith") ||
				!strings.Contains(err.Error(), "sharing rule must target exactly one role or group") {
				t.Fatalf("runRecordWriteValidators error = %v, want SharingRule shape validation", err)
			}
		})
	}
}

func TestBuiltInUserRoleParentWriteValidatorUsesMetadataCapability(t *testing.T) {
	const capability = "platform.security.write.user-role-parent"
	tests := []struct {
		name       string
		phase      contract.RecordWriteValidationPhase
		internal   recordWriteValidationPhase
		recordID   string
		record     models.SObject
		repoRecord models.SObject
		want       string
	}{
		{
			name:     "before create rejects self parent",
			phase:    contract.RecordWriteValidationBeforeCreate,
			internal: recordWriteValidationBeforeCreate,
			recordID: "00E-new",
			record: models.SObject{
				constants.FieldID:                    "00E-new",
				constants.FieldUserRole_ParentRoleID: "00E-new",
			},
			want: "ParentRoleId cannot reference self",
		},
		{
			name:     "before update rejects self parent",
			phase:    contract.RecordWriteValidationBeforeUpdate,
			internal: recordWriteValidationBeforeUpdate,
			recordID: "00E-child",
			record: models.SObject{
				constants.FieldID:                    "00E-child",
				constants.FieldUserRole_ParentRoleID: "00E-child",
			},
			want: "ParentRoleId cannot reference self",
		},
		{
			name:     "before update rejects parent cycle",
			phase:    contract.RecordWriteValidationBeforeUpdate,
			internal: recordWriteValidationBeforeUpdate,
			recordID: "00E-child",
			record: models.SObject{
				constants.FieldID:                    "00E-child",
				constants.FieldUserRole_ParentRoleID: "00E-parent",
			},
			repoRecord: models.SObject{
				constants.FieldID:                    "00E-parent",
				constants.FieldUserRole_ParentRoleID: "00E-child",
			},
			want: "ParentRoleId would create a cycle in role hierarchy",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, capability)
			schema.APIName = constants.TableUserRole
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{old: tt.repoRecord})

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TableUserRole,
				RecordID:   tt.recordID,
				Record:     tt.record,
				OldRecord: models.SObject{
					constants.FieldID: tt.recordID,
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want UserRole parent validation error")
			}
			if !strings.Contains(err.Error(), tt.want) {
				t.Fatalf("runRecordWriteValidators error = %v, want %q", err, tt.want)
			}
		})
	}
}

func TestBuiltInUserRoleParentWriteValidatorUsesPendingCreates(t *testing.T) {
	const capability = "platform.security.write.user-role-parent"
	schema := writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeCreate, capability)
	schema.APIName = constants.TableUserRole
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableUserRole,
		RecordID:   "00E-A",
		Record: models.SObject{
			constants.FieldID:                    "00E-A",
			constants.FieldUserRole_ParentRoleID: "00E-B",
		},
		PendingCreates: []recordWriteValidationPendingCreate{
			{
				RecordID: "00E-A",
				Record: models.SObject{
					constants.FieldID:                    "00E-A",
					constants.FieldUserRole_ParentRoleID: "00E-B",
				},
			},
			{
				RecordID: "00E-B",
				Record: models.SObject{
					constants.FieldID:                    "00E-B",
					constants.FieldUserRole_ParentRoleID: "00E-A",
				},
			},
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want pending same-batch UserRole parent cycle rejection")
	}
	if !strings.Contains(err.Error(), "ParentRoleId would create a cycle in role hierarchy") {
		t.Fatalf("runRecordWriteValidators error = %v, want same-batch UserRole cycle rejection", err)
	}
}

func TestBuiltInUserRoleDeleteValidatorUsesMetadataCapability(t *testing.T) {
	const capability = "platform.security.write.user-role-parent"

	tests := []struct {
		name     string
		children map[string][]models.SObject
		want     string
	}{
		{
			name: "rejects child roles",
			children: map[string][]models.SObject{
				constants.TableUserRole + "|" + constants.FieldUserRole_ParentRoleID: {{
					constants.FieldID:        "00E-child",
					constants.FieldIsDeleted: false,
				}},
			},
			want: userRoleDeleteChildRoleMessage,
		},
		{
			name: "rejects assigned users",
			children: map[string][]models.SObject{
				constants.TableUser + "|" + constants.FieldUserRoleID: {{
					constants.FieldID: "005-user",
				}},
			},
			want: userRoleDeleteAssignedUsersMessage,
		},
		{
			name: "ignores deleted references",
			children: map[string][]models.SObject{
				constants.TableUserRole + "|" + constants.FieldUserRole_ParentRoleID: {{
					constants.FieldID:        "00E-deleted-child",
					constants.FieldIsDeleted: true,
				}},
				constants.TableUser + "|" + constants.FieldUserRoleID: {{
					constants.FieldID:        "005-deleted-user",
					constants.FieldIsDeleted: 1,
				}},
			},
		},
		{
			name: "allows unreferenced role",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeDelete, capability)
			schema.APIName = constants.TableUserRole
			repo := &captureRecordRepo{
				getChildrenFn: func(_ context.Context, childTable string, foreignKey string, parentID string) ([]models.SObject, error) {
					if parentID != "00E-role" {
						t.Fatalf("GetChildren parentID = %q, want 00E-role", parentID)
					}
					return cloneSObjects(tt.children[childTable+"|"+foreignKey]), nil
				},
			}
			ps := newRecordEntrypointTestService(schema, repo)

			err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeDelete, recordWriteValidationEvent{
				ObjectName: constants.TableUserRole,
				RecordID:   "00E-role",
				Record: models.SObject{
					constants.FieldID: "00E-role",
				},
				OldRecord: models.SObject{
					constants.FieldID: "00E-role",
				},
			})
			if tt.want == "" {
				if err != nil {
					t.Fatalf("runRecordWriteValidators error = %v, want nil", err)
				}
				return
			}
			if err == nil {
				t.Fatalf("runRecordWriteValidators succeeded, want %q", tt.want)
			}
			if !strings.Contains(err.Error(), tt.want) {
				t.Fatalf("runRecordWriteValidators error = %v, want %q", err, tt.want)
			}
		})
	}
}

func TestUserDeclaresDeleteLifecycleWriteValidation(t *testing.T) {
	required := securityWriteValidationMetadataRequiredObjectsFromDefinitions(managedTableDefinitions())
	key, ok := securityWriteValidationMetadataRequirementKey(constants.TableUser, contract.RecordWriteValidationBeforeDelete, securityWriteValidateUserDeleteLifecycleCapability)
	if !ok {
		t.Fatalf("required-set key for %s/%s not constructed", constants.TableUser, securityWriteValidateUserDeleteLifecycleCapability)
	}
	if _, found := required[key]; !found {
		t.Fatalf("required-set missing %s for %s before_delete", securityWriteValidateUserDeleteLifecycleCapability, constants.TableUser)
	}
}

func TestBuiltInUserDeleteLifecycleValidatorUsesMetadataCapability(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeDelete, securityWriteValidateUserDeleteLifecycleCapability)
	schema.APIName = constants.TableUser
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeDelete, recordWriteValidationEvent{
		ObjectName: constants.TableUser,
		RecordID:   "005-user",
		Record: models.SObject{
			constants.FieldID: "005-user",
		},
		OldRecord: models.SObject{
			constants.FieldID: "005-user",
		},
	})
	if err == nil {
		t.Fatalf("runRecordWriteValidators succeeded, want user delete lifecycle rejection")
	}
	if !strings.Contains(err.Error(), userDeleteLifecycleMessage) {
		t.Fatalf("runRecordWriteValidators error = %v, want %q", err, userDeleteLifecycleMessage)
	}
}

type permissionSetAssignmentValidationRepo struct {
	*captureRecordRepo
}

func (r *permissionSetAssignmentValidationRepo) FindAny(ctx context.Context, tableName string, id string) (models.SObject, error) {
	return r.captureRecordRepo.FindOne(ctx, tableName, id)
}

func permissionSetAssignmentValidationContext(permissionSetID string) context.Context {
	ctx := recordlocks.WithScope(context.Background())
	recordlocks.ScopeFromContext(ctx).MarkAcquired(recordlocks.ParentKey{
		ObjectName: constants.TablePermissionSet,
		RecordID:   permissionSetID,
	})
	return ctx
}

func TestBuiltInPermissionSetAssignmentWriteValidatorRequiresTargetLock(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		securityWriteValidatePermissionSetAssignmentTargetCapability,
	)
	schema.APIName = constants.TablePermissionSetAssignment
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(schema, repo)
	ps.repo = &permissionSetAssignmentValidationRepo{captureRecordRepo: repo}

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TablePermissionSetAssignment,
		Record: models.SObject{
			constants.FieldPermissionSetAssignment_PermissionSetID: "ps-custom",
		},
	})
	if err == nil || !strings.Contains(err.Error(), "requires parent-lock coordination") {
		t.Fatalf("runRecordWriteValidators error = %v, want missing parent-lock scope", err)
	}
}

func TestBuiltInPermissionSetAssignmentWriteValidatorUsesMetadataCapability(t *testing.T) {
	const capability = "platform.security.write.permission-set-assignment-target"
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{name: "before create", phase: contract.RecordWriteValidationBeforeCreate, internal: recordWriteValidationBeforeCreate},
		{name: "before update", phase: contract.RecordWriteValidationBeforeUpdate, internal: recordWriteValidationBeforeUpdate},
		{name: "before delete", phase: contract.RecordWriteValidationBeforeDelete, internal: recordWriteValidationBeforeDelete},
		{name: "before restore", phase: contract.RecordWriteValidationBeforeRestore, internal: recordWriteValidationBeforeRestore},
		{name: "before purge", phase: contract.RecordWriteValidationBeforePurge, internal: recordWriteValidationBeforePurge},
	}

	for _, tt := range tests {
		t.Run(tt.name+" rejects profile owned", func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, capability)
			schema.APIName = constants.TablePermissionSetAssignment
			repo := &captureRecordRepo{old: models.SObject{
				constants.FieldID: "ps-profile-owned",
				constants.FieldPermissionSet_IsOwnedByProfile: true,
				constants.FieldPermissionSet_IsActive:         true,
				constants.FieldIsDeleted:                      false,
			}}
			ps := newRecordEntrypointTestService(schema, repo)
			ps.repo = &permissionSetAssignmentValidationRepo{captureRecordRepo: repo}

			err := ps.runRecordWriteValidators(permissionSetAssignmentValidationContext("ps-profile-owned"), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TablePermissionSetAssignment,
				RecordID:   "0Pa-test",
				Record: models.SObject{
					constants.FieldID: "0Pa-test",
					constants.FieldPermissionSetAssignment_PermissionSetID: "ps-profile-owned",
				},
				OldRecord: models.SObject{
					constants.FieldID: "0Pa-test",
					constants.FieldPermissionSetAssignment_PermissionSetID: "ps-profile-owned",
				},
			})
			if err == nil {
				t.Fatal("runRecordWriteValidators succeeded, want profile-owned PermissionSetAssignment rejection")
			}
			if !strings.Contains(err.Error(), profileOwnedPermissionSetAssignmentMessage) {
				t.Fatalf("runRecordWriteValidators error = %v, want profile-owned assignment message", err)
			}
		})

		t.Run(tt.name+" allows non profile owned", func(t *testing.T) {
			schema := writeValidationProbeSchemaForPhase(tt.phase, capability)
			schema.APIName = constants.TablePermissionSetAssignment
			repo := &captureRecordRepo{old: models.SObject{
				constants.FieldID: "ps-custom",
				constants.FieldPermissionSet_IsOwnedByProfile: false,
				constants.FieldPermissionSet_IsActive:         true,
				constants.FieldIsDeleted:                      false,
			}}
			ps := newRecordEntrypointTestService(schema, repo)
			ps.repo = &permissionSetAssignmentValidationRepo{captureRecordRepo: repo}
			ps.assignmentPolicyAuthority = &activeClaimAuthorityTestGuard{}

			err := ps.runRecordWriteValidators(permissionSetAssignmentValidationContext("ps-custom"), tt.internal, recordWriteValidationEvent{
				ObjectName: constants.TablePermissionSetAssignment,
				RecordID:   "0Pa-test",
				Record: models.SObject{
					constants.FieldID: "0Pa-test",
					constants.FieldPermissionSetAssignment_PermissionSetID: "ps-custom",
				},
				OldRecord: models.SObject{
					constants.FieldID: "0Pa-test",
					constants.FieldPermissionSetAssignment_PermissionSetID: "ps-custom",
				},
			})
			if err != nil {
				t.Fatalf("runRecordWriteValidators returned error for non-profile-owned permission set: %v", err)
			}
		})
	}
}

func TestRecordWriteValidatorRegistrationRunsBeforeUpdateSQL(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeUpdate, "test.write.validate"),
		repo,
	)
	sentinel := errors.New("blocked by update validator")
	var called bool

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeUpdate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			called = true
			if repo.updated != nil {
				t.Fatal("write validator ran after repo.Update; want pre-SQL validation")
			}
			if event.RecordID != "probe-1" {
				t.Fatalf("RecordID = %q, want probe-1", event.RecordID)
			}
			if event.OldRecord[constants.FieldName] != "old" {
				t.Fatalf("OldRecord Name = %v, want old", event.OldRecord[constants.FieldName])
			}
			if event.Record[constants.FieldName] != "new" {
				t.Fatalf("Record Name = %v, want new", event.Record[constants.FieldName])
			}
			return sentinel
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	err := ps.Update(context.Background(), "WriteValidationProbe__c", "probe-1", models.SObject{
		constants.FieldName: "new",
	}, recordEntrypointUser())
	if !errors.Is(err, sentinel) {
		t.Fatalf("Update error = %v, want sentinel validator error", err)
	}
	if !called {
		t.Fatal("write validator was not called")
	}
	if repo.updated != nil {
		t.Fatalf("repo.Update ran after failing write validator: %#v", repo.updated)
	}
}

func TestRecordWriteValidatorRegistrationDoesNotMutateUpdateRecord(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeUpdate, "test.write.normalize"),
		repo,
	)

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.normalize",
		Phase:      contract.RecordWriteValidationBeforeUpdate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			event.Record[constants.FieldName] = "normalized"
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	err := ps.Update(context.Background(), "WriteValidationProbe__c", "probe-1", models.SObject{
		constants.FieldName: "raw",
	}, recordEntrypointUser())
	if err != nil {
		t.Fatalf("Update returned error: %v", err)
	}
	if repo.updated[constants.FieldName] != "raw" {
		t.Fatalf("updated Name = %v, want raw; updates=%#v", repo.updated[constants.FieldName], repo.updated)
	}
}

func TestRecordWriteValidatorRegistrationRunsDuringPreviewBeforeUpdate(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeUpdate, "test.write.validate"),
		repo,
	)
	sentinel := errors.New("blocked by preview validator")
	var called bool

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeUpdate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			called = true
			if event.RecordID != "probe-1" {
				t.Fatalf("RecordID = %q, want probe-1", event.RecordID)
			}
			if event.OldRecord[constants.FieldName] != "old" {
				t.Fatalf("OldRecord Name = %v, want old", event.OldRecord[constants.FieldName])
			}
			if event.Record[constants.FieldName] != "new" {
				t.Fatalf("Record Name = %v, want new", event.Record[constants.FieldName])
			}
			return sentinel
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	_, err := ps.PreviewBeforeUpdate(context.Background(), "WriteValidationProbe__c", "probe-1", models.SObject{
		constants.FieldName: "new",
	}, nil, recordEntrypointUser())
	if !errors.Is(err, sentinel) {
		t.Fatalf("PreviewBeforeUpdate error = %v, want sentinel validator error", err)
	}
	if !called {
		t.Fatal("write validator was not called")
	}
	if repo.updated != nil {
		t.Fatalf("repo.Update ran during preview: %#v", repo.updated)
	}
}

func TestRecordWriteValidatorCommitPreparedUpdateRunsOnceOnFinalImage(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeUpdate, "test.write.final"),
		repo,
	)
	var calls int

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.final",
		Phase:      contract.RecordWriteValidationBeforeUpdate,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			calls++
			if event.OldRecord[constants.FieldName] != "old" {
				t.Fatalf("OldRecord Name = %v, want old", event.OldRecord[constants.FieldName])
			}
			if event.Record[constants.FieldName] != "final" {
				t.Fatalf("Record Name = %v, want final image", event.Record[constants.FieldName])
			}
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	prepared := &models.PreparedRecordUpdate{
		Record:           models.SObject{constants.FieldID: "probe-1", constants.FieldName: "preview"},
		EffectiveUpdates: models.SObject{constants.FieldName: "preview"},
	}
	if err := ps.CommitPreparedUpdate(context.Background(), "WriteValidationProbe__c", "probe-1", prepared, models.SObject{
		constants.FieldName: "final",
	}, recordEntrypointUser()); err != nil {
		t.Fatalf("CommitPreparedUpdate returned error: %v", err)
	}
	if calls != 1 {
		t.Fatalf("validator calls = %d, want 1 final-image invocation", calls)
	}
}

func TestRecordWriteValidatorRegistrationSupportsRecycleBinPhases(t *testing.T) {
	tests := []struct {
		name     string
		phase    contract.RecordWriteValidationPhase
		internal recordWriteValidationPhase
	}{
		{
			name:     "before restore",
			phase:    contract.RecordWriteValidationBeforeRestore,
			internal: recordWriteValidationBeforeRestore,
		},
		{
			name:     "before purge",
			phase:    contract.RecordWriteValidationBeforePurge,
			internal: recordWriteValidationBeforePurge,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			const capability = "test.write.recycle-bin"
			ps := newRecordEntrypointTestService(
				writeValidationProbeSchemaForPhase(tt.phase, capability),
				&captureRecordRepo{},
			)
			var called bool
			if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
				Capability: capability,
				Phase:      tt.phase,
				Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
					called = true
					if event.ObjectName != "WriteValidationProbe__c" {
						t.Fatalf("ObjectName = %q, want WriteValidationProbe__c", event.ObjectName)
					}
					if event.RecordID != "probe-1" {
						t.Fatalf("RecordID = %q, want probe-1", event.RecordID)
					}
					return nil
				},
			}); err != nil {
				t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
			}

			err := ps.runRecordWriteValidators(context.Background(), tt.internal, recordWriteValidationEvent{
				ObjectName: "WriteValidationProbe__c",
				RecordID:   "probe-1",
				Record: models.SObject{
					constants.FieldID: "probe-1",
				},
				OldRecord: models.SObject{
					constants.FieldID: "probe-1",
				},
			})
			if err != nil {
				t.Fatalf("runRecordWriteValidators returned error: %v", err)
			}
			if !called {
				t.Fatal("registered recycle-bin write validator was not called")
			}
		})
	}
}

func TestRecordWriteValidatorRegistrationRunsBeforeDeleteSQL(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeDelete, "test.write.validate"),
		repo,
	)
	sentinel := errors.New("blocked by delete validator")
	var called bool

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeDelete,
		Validator: func(_ context.Context, event contract.RecordWriteValidationEvent) error {
			called = true
			if repo.updated != nil {
				t.Fatal("write validator ran after delete repo.Update; want pre-SQL validation")
			}
			if event.RecordID != "probe-1" {
				t.Fatalf("RecordID = %q, want probe-1", event.RecordID)
			}
			if event.Record[constants.FieldName] != "old" {
				t.Fatalf("Record Name = %v, want old", event.Record[constants.FieldName])
			}
			if event.OldRecord[constants.FieldName] != "old" {
				t.Fatalf("OldRecord Name = %v, want old", event.OldRecord[constants.FieldName])
			}
			return sentinel
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "write validator delete test")
	err := ps.Delete(ctx, "WriteValidationProbe__c", "probe-1", recordEntrypointUser())
	if !errors.Is(err, sentinel) {
		t.Fatalf("Delete error = %v, want sentinel validator error", err)
	}
	if !called {
		t.Fatal("write validator was not called")
	}
	if repo.updated != nil {
		t.Fatalf("repo.Update ran after failing write validator: %#v", repo.updated)
	}
}

func TestRecordWriteValidatorRejectsBeforeDeleteEventPublish(t *testing.T) {
	repo := &captureRecordRepo{
		old: models.SObject{
			constants.FieldID:   "probe-1",
			constants.FieldName: "old",
		},
	}
	ps := newRecordEntrypointTestService(
		writeValidationProbeSchemaForPhase(contract.RecordWriteValidationBeforeDelete, "test.write.validate"),
		repo,
	)
	sentinel := errors.New("blocked before delete event")
	var beforeDeletePublishes int
	ps.eventBus = lifecycleSpikeEventBus{publish: func(_ context.Context, eventType sharedEvents.EventType, _ interface{}) error {
		if eventType == sharedEvents.RecordBeforeDelete {
			beforeDeletePublishes++
		}
		return nil
	}}

	if err := ps.RegisterRecordWriteValidator(contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeDelete,
		Validator:  func(context.Context, contract.RecordWriteValidationEvent) error { return sentinel },
	}); err != nil {
		t.Fatalf("RegisterRecordWriteValidator returned error: %v", err)
	}

	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "write validator delete event test")
	err := ps.Delete(ctx, "WriteValidationProbe__c", "probe-1", recordEntrypointUser())
	if !errors.Is(err, sentinel) {
		t.Fatalf("Delete error = %v, want sentinel validator error", err)
	}
	if beforeDeletePublishes != 0 {
		t.Fatalf("RecordBeforeDelete publishes = %d, want 0 when before_delete validator rejects", beforeDeletePublishes)
	}
	if repo.updated != nil {
		t.Fatalf("repo.Update ran after failing write validator: %#v", repo.updated)
	}
}

func TestRecordWriteValidatorRegistrationRejectsDuplicateBinding(t *testing.T) {
	ps := newRecordEntrypointTestService(writeValidationProbeSchema(), &captureRecordRepo{})
	reg := contract.RecordWriteValidatorRegistration{
		Capability: "test.write.validate",
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Validator:  func(context.Context, contract.RecordWriteValidationEvent) error { return nil },
	}
	if err := ps.RegisterRecordWriteValidator(reg); err != nil {
		t.Fatalf("first RegisterRecordWriteValidator returned error: %v", err)
	}
	if err := ps.RegisterRecordWriteValidator(reg); err == nil {
		t.Fatal("duplicate RegisterRecordWriteValidator succeeded, want fail-closed error")
	}
}

func TestRecordWriteCanonicalizerRegistrationMutatesDeclaredRecord(t *testing.T) {
	const capability = "test.write.canonicalize"
	schema := writeValidationProbeSchema()
	schema.AccessPolicy = &models.ObjectAccessPolicy{
		WriteValidation: &models.RecordWriteValidationAccessPolicy{},
	}
	schema.AccessPolicy.WriteValidation.Canonicalizers = []models.RecordWriteCanonicalizationPolicy{{
		Phase:      string(contract.RecordWriteValidationBeforeCreate),
		Capability: capability,
	}}
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	if err := ps.RegisterRecordWriteCanonicalizer(contract.RecordWriteCanonicalizerRegistration{
		Capability: capability,
		Phase:      contract.RecordWriteValidationBeforeCreate,
		Canonicalizer: func(_ context.Context, event contract.RecordWriteCanonicalizationEvent) error {
			event.Record["Normalized"] = "yes"
			delete(event.Record, "RemoveMe")
			event.EffectiveUpdates["Diff"] = nil
			return nil
		},
	}); err != nil {
		t.Fatalf("RegisterRecordWriteCanonicalizer returned error: %v", err)
	}
	record := models.SObject{"RemoveMe": "drop"}
	effectiveUpdates := models.SObject{}

	err := ps.runRecordWriteCanonicalizers(context.Background(), recordWriteValidationBeforeCreate, recordWriteCanonicalizationEvent{
		ObjectName:       "Thing",
		Record:           record,
		EffectiveUpdates: effectiveUpdates,
	})
	if err != nil {
		t.Fatalf("runRecordWriteCanonicalizers returned error: %v", err)
	}
	if _, exists := record["RemoveMe"]; exists {
		t.Fatalf("record still has RemoveMe after canonicalizer: %#v", record)
	}
	if got := record["Normalized"]; got != "yes" {
		t.Fatalf("record Normalized = %#v, want yes", got)
	}
	if got, exists := effectiveUpdates["Diff"]; !exists || got != nil {
		t.Fatalf("effectiveUpdates Diff = %#v (exists=%v), want explicit nil", got, exists)
	}
}

func TestRecordWriteValidatorRegistrationRejectsInvalidRegistration(t *testing.T) {
	ps := newRecordEntrypointTestService(writeValidationProbeSchema(), &captureRecordRepo{})
	tests := []struct {
		name string
		reg  contract.RecordWriteValidatorRegistration
	}{
		{
			name: "missing capability",
			reg: contract.RecordWriteValidatorRegistration{
				Phase:     contract.RecordWriteValidationBeforeCreate,
				Validator: func(context.Context, contract.RecordWriteValidationEvent) error { return nil },
			},
		},
		{
			name: "missing validator",
			reg: contract.RecordWriteValidatorRegistration{
				Capability: "test.write.validate",
				Phase:      contract.RecordWriteValidationBeforeCreate,
			},
		},
		{
			name: "unsupported phase",
			reg: contract.RecordWriteValidatorRegistration{
				Capability: "test.write.validate",
				Phase:      contract.RecordWriteValidationPhase("after_create"),
				Validator:  func(context.Context, contract.RecordWriteValidationEvent) error { return nil },
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ps.RegisterRecordWriteValidator(tt.reg); err == nil {
				t.Fatal("RegisterRecordWriteValidator succeeded, want error")
			}
		})
	}
}

func TestBuiltInObjectPermissionsWriteValidatorUsesMetadataCapability(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		securityWriteValidateObjectPermissionsCapability,
		securityWriteValidateSystemProfileOwnedImmutableCapability,
		securityWriteValidateManagedPermissionSetDefinitionCapability,
	)
	schema.APIName = constants.TableObjectPermissions
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	record := models.SObject{
		constants.FieldObjectPermissions_PermissionsRead:             false,
		constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
	}

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableObjectPermissions,
		RecordID:   "110000000000001",
		Record:     record,
	})

	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want object permission dependency validation error")
	}
	if !strings.Contains(err.Error(), "Modify All") {
		t.Fatalf("runRecordWriteValidators error = %v, want ObjectPermissions dependency validation", err)
	}
}

func TestBuiltInFieldPermissionsWriteValidatorUsesMetadataCapability(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		securityWriteValidateFieldPermissionsCapability,
		securityWriteValidateSystemProfileOwnedImmutableCapability,
		securityWriteValidateManagedPermissionSetDefinitionCapability,
	)
	schema.APIName = constants.TableFieldPermissions
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
	record := models.SObject{
		constants.FieldFieldPermissions_SobjectType:     "Account",
		constants.FieldFieldPermissions_Field:           "Account.Name",
		constants.FieldFieldPermissions_PermissionsRead: false,
		constants.FieldFieldPermissions_PermissionsEdit: true,
	}

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableFieldPermissions,
		RecordID:   "01k000000000001",
		Record:     record,
	})

	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want field permission dependency validation error")
	}
	if !strings.Contains(err.Error(), "Edit Account.Name requires Read Account.Name") {
		t.Fatalf("runRecordWriteValidators error = %v, want FieldPermissions dependency validation", err)
	}
}

func TestFieldPermissionsWriteValidatorRejectsUnknownMetadataTargets(t *testing.T) {
	account := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "Name", Type: constants.FieldTypeString},
		},
	}
	ps := &PersistenceService{metadata: metadataFeature.NewMetadataService(
		&stubUpsertMetaRepo{schemas: []*models.ObjectMetadata{account}}, nil, nil,
	)}

	tests := []struct {
		name        string
		sobjectType string
		field       string
		wantError   string
	}{
		{name: "simple field", sobjectType: "Account", field: "Name"},
		{name: "qualified field is case insensitive", sobjectType: "account", field: "ACCOUNT.name"},
		{name: "unknown object", sobjectType: "Contact", field: "Contact.Name", wantError: "unknown SobjectType Contact"},
		{name: "mismatched qualified object", sobjectType: "Account", field: "Contact.Name", wantError: "does not belong to Account"},
		{name: "unknown field", sobjectType: "Account", field: "Account.DoesNotExist", wantError: "unknown field Account.DoesNotExist"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := ps.validateFieldPermissionsWriteContract(t.Context(), contract.RecordWriteValidationEvent{
				ObjectName: constants.TableFieldPermissions,
				Record: contract.Record{
					constants.FieldFieldPermissions_SobjectType:     test.sobjectType,
					constants.FieldFieldPermissions_Field:           test.field,
					constants.FieldFieldPermissions_PermissionsRead: true,
				},
			})
			if test.wantError == "" {
				if err != nil {
					t.Fatalf("validateFieldPermissionsWriteContract() error = %v", err)
				}
				return
			}
			if err == nil || !strings.Contains(err.Error(), test.wantError) {
				t.Fatalf("validateFieldPermissionsWriteContract() error = %v, want %q", err, test.wantError)
			}
		})
	}
}

func TestObjectPermissionsBulkInsertRunsWriteValidatorBeforeSQL(t *testing.T) {
	repo := &captureRecordRepo{}
	ps := newRecordEntrypointTestService(objectPermissionsEntrypointSchema(), repo)

	result, err := ps.BulkInsert(context.Background(), constants.TableObjectPermissions, []models.SObject{
		{
			constants.FieldObjectPermissions_ParentID:                    "0PS000000000001",
			constants.FieldObjectPermissions_SobjectType:                 "Account",
			constants.FieldObjectPermissions_PermissionsRead:             false,
			constants.FieldObjectPermissions_PermissionsModifyAllRecords: true,
		},
		{
			constants.FieldObjectPermissions_ParentID:                    "0PS000000000002",
			constants.FieldObjectPermissions_SobjectType:                 "Contact",
			constants.FieldObjectPermissions_PermissionsRead:             true,
			constants.FieldObjectPermissions_PermissionsCreate:           false,
			constants.FieldObjectPermissions_PermissionsEdit:             false,
			constants.FieldObjectPermissions_PermissionsDelete:           false,
			constants.FieldObjectPermissions_PermissionsViewAllRecords:   false,
			constants.FieldObjectPermissions_PermissionsModifyAllRecords: false,
		},
	}, recordEntrypointUser(), BulkInsertOptions{})
	if err != nil {
		t.Fatalf("BulkInsert returned error: %v", err)
	}
	if result.SuccessCount != 1 || result.FailedCount != 1 {
		t.Fatalf("BulkInsert result = %+v, want one valid ObjectPermissions row and one write-validation failure", result)
	}
	if len(result.Errors) != 1 || !strings.Contains(result.Errors[0], "Modify All") {
		t.Fatalf("BulkInsert errors = %+v, want ObjectPermissions dependency validation failure", result.Errors)
	}
	if len(repo.bulkInserted) != 1 {
		t.Fatalf("repo.BulkInsert rows = %d, want only the valid row after write validation", len(repo.bulkInserted))
	}
	if repo.bulkInserted[0][constants.FieldObjectPermissions_SobjectType] != "Contact" {
		t.Fatalf("bulk inserted row = %#v, want valid Contact ObjectPermissions row only", repo.bulkInserted[0])
	}
}

func TestBuiltInSystemProfileOwnedImmutableWriteValidatorUsesMetadataCapability(t *testing.T) {
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		securityWriteValidateSystemProfileOwnedImmutableCapability,
	)
	schema.APIName = constants.TableProfileCustomPermission
	// ProfileCustomPermission is a migrated single-hop table: its schema must
	// declare the ownerResolution spec so the seam routes through the
	// metadata-driven resolver instead of the (removed) legacy switch case.
	schema.AccessPolicy.WriteValidation.OwnerResolution = systemProfileOwnedImmutableOwnerResolutionSpec()
	repo := &captureRecordRepo{
		findOneFn: func(_ context.Context, tableName string, id string) (models.SObject, error) {
			if strings.EqualFold(tableName, constants.TableProfile) && id == "00E-sys-profile" {
				return models.SObject{
					constants.FieldID:               "00E-sys-profile",
					constants.FieldProfile_IsSystem: true,
				}, nil
			}
			return nil, nil
		},
	}
	ps := newRecordEntrypointTestService(schema, repo)

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableProfileCustomPermission,
		Record: models.SObject{
			constants.FieldID: "lock-test",
			constants.FieldProfileCustomPermission_ProfileID: "00E-sys-profile",
		},
	})
	if err == nil {
		t.Fatal("runRecordWriteValidators succeeded, want system-profile-owned rejection")
	}
	if !strings.Contains(err.Error(), systemProfileOwnedPermissionResourceLockedMessage) {
		t.Fatalf("runRecordWriteValidators error = %v, want %q", err, systemProfileOwnedPermissionResourceLockedMessage)
	}
}

func TestSystemProfileOwnedImmutableFailsClosedWhenOwnerResolutionUnreconciled(t *testing.T) {
	// Stale-schema simulation: the schema carries the immutability capability
	// (so the required-set gate passes) but omits the ownerResolution subfact —
	// the state an upgraded org serves in when auto-start skips setup apply
	// before the ownerResolution subfact is reconciled into the persisted
	// _System_Object.AccessPolicy. ProfileTabSetting's managed definition
	// declares ownerResolution, so the guard must fail closed rather than fall
	// through the pruned switch and silently allow the write.
	schema := writeValidationProbeSchemaForPhase(
		contract.RecordWriteValidationBeforeCreate,
		securityWriteValidateSystemProfileOwnedImmutableCapability,
	)
	schema.APIName = constants.TableProfileTabSetting
	ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})

	err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
		ObjectName: constants.TableProfileTabSetting,
		Record: models.SObject{
			constants.FieldID:                          "stale-test",
			constants.FieldProfileTabSetting_ProfileID: "profile-system",
		},
	})
	if err == nil {
		t.Fatal("expected fail-closed error when ownerResolution is not reconciled")
	}
	if !strings.Contains(err.Error(), "owner-resolution metadata not reconciled") {
		t.Fatalf("expected owner-resolution reconciliation fail-closed error, got %v", err)
	}
}

func TestSystemProfileOwnedImmutableRequiredAcrossSecurityTables(t *testing.T) {
	required := securityWriteValidationMetadataRequiredObjectsFromDefinitions(managedTableDefinitions())
	tables := []string{
		constants.TablePermissionSet, constants.TableObjectPermissions, constants.TableFieldPermissions,
		constants.TableProfilePermission, constants.TableProfileCustomPermission, constants.TableProfileTabSetting,
		constants.TableProfileAppVisibility, constants.TablePermissionSetPermission, constants.TablePermissionSetCustomPermission,
		constants.TablePermissionSetRecordTypeVisibility, constants.TablePermissionSetTabSetting,
		constants.TablePermissionSetAppVisibility, constants.TableConnectedAppAccess,
	}
	phases := []contract.RecordWriteValidationPhase{
		contract.RecordWriteValidationBeforeCreate, contract.RecordWriteValidationBeforeUpdate,
		contract.RecordWriteValidationBeforeDelete, contract.RecordWriteValidationBeforeRestore,
		contract.RecordWriteValidationBeforePurge,
	}
	for _, table := range tables {
		for _, phase := range phases {
			key, ok := securityWriteValidationMetadataRequirementKey(table, phase, securityWriteValidateSystemProfileOwnedImmutableCapability)
			if !ok {
				t.Fatalf("required-set key for %s/%s not constructed", table, phase)
			}
			if _, found := required[key]; !found {
				t.Errorf("required-set missing %s for %s %s", securityWriteValidateSystemProfileOwnedImmutableCapability, table, phase)
			}
		}
	}
}

func TestSystemProfileOwnedImmutableFailsClosedWhenDeclarationDropped(t *testing.T) {
	tables := []string{
		constants.TablePermissionSet, constants.TableObjectPermissions, constants.TableFieldPermissions,
		constants.TableProfilePermission, constants.TableProfileCustomPermission, constants.TableProfileTabSetting,
		constants.TableProfileAppVisibility, constants.TablePermissionSetPermission, constants.TablePermissionSetCustomPermission,
		constants.TablePermissionSetRecordTypeVisibility, constants.TablePermissionSetTabSetting,
		constants.TablePermissionSetAppVisibility, constants.TableConnectedAppAccess,
	}
	for _, table := range tables {
		t.Run(table, func(t *testing.T) {
			schema := writeValidationProbeSchema()
			schema.APIName = table
			schema.AccessPolicy = nil
			ps := newRecordEntrypointTestService(schema, &captureRecordRepo{})
			err := ps.runRecordWriteValidators(context.Background(), recordWriteValidationBeforeCreate, recordWriteValidationEvent{
				ObjectName: table,
				Record:     models.SObject{constants.FieldID: "drop-test"},
			})
			if err == nil {
				t.Fatalf("runRecordWriteValidators succeeded for %s after dropping the declaration, want fail-closed error", table)
			}
			if !strings.Contains(err.Error(), "missing record write validation metadata") {
				t.Fatalf("error = %v, want missing metadata error for %s", err, table)
			}
		})
	}
}
