package catalogsnapshot

import (
	"bytes"
	"encoding/json"
	"fmt"
	"sort"
	"strings"

	"github.com/nexuscrm/kernel/pkg/formula"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldcapabilities"
	"github.com/nexuscrm/shared/pkg/formulastorage"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/catalogassignmentidentityv1"
)

func CompileCatalogSnapshot(input CompileInput) (Artifact, error) {
	if err := validateLineage(input.Lineage); err != nil {
		return Artifact{}, err
	}
	if err := requireCanonicalIdentity("orgId", input.OrgID); err != nil {
		return Artifact{}, err
	}
	if (len(input.AttachmentSourceResolvers) != 0 || len(input.AttachmentSourceFields) != 0) && !inputHasRecordViewProjection(input.Projections) {
		return Artifact{}, conflict(ConflictInvalidAttachmentSource, "recordView", "attachment source facts require an explicit record-view projection")
	}
	pageLayoutResolutionSelected := inputHasPageLayoutResolutionProjection(input.Projections)
	layoutAuthoringSelected := inputHasLayoutAuthoringProjection(input.Projections)
	hasPageLayoutResolutionFacts := len(input.PageLayouts) != 0 || len(input.LayoutAssignments) != 0 ||
		len(input.PageLayoutRecordTypes) != 0 || len(input.RelatedListResolutions) != 0
	if hasPageLayoutResolutionFacts && !pageLayoutResolutionSelected {
		return Artifact{}, conflict(ConflictInvalidProjection, "pageLayoutResolution", "caller-effective Layout facts require the page-layout resolution projection")
	}
	if len(input.LayoutAuthoringAggregates) != 0 && !layoutAuthoringSelected {
		return Artifact{}, conflict(ConflictInvalidProjection, "layoutAuthoring", "stored Layout aggregates require the Layout authoring projection")
	}

	schema := CatalogSnapshotSchemaV2
	if len(input.AttachmentSourceResolvers) != 0 || len(input.AttachmentSourceFields) != 0 || inputHasRecordViewProjection(input.Projections) {
		// V6 is the first record-view grammar that carries a Catalog-declared
		// source-record matcher. V5 remains readable only so historical,
		// non-serving artifacts can be verified; new compilation must never
		// recreate its opaque whole-ID behavior.
		schema = CatalogSnapshotSchemaV6
	} else if len(input.MutationDefinitions) != 0 || inputHasMutationProjection(input.Projections) ||
		inputHasEffectivePermissionProjection(input.Projections) {
		schema = CatalogSnapshotSchemaV4
	}
	if len(input.ReportDefinitions) != 0 || inputHasReportExecutionProjection(input.Projections) {
		schema = CatalogSnapshotSchemaV8
	}
	if len(input.CommandRegistrations) != 0 || inputHasCommandAcceptanceProjection(input.Projections) {
		schema = CatalogSnapshotSchemaV9
	}
	if inputHasCommandExecutionBindings(input.CommandRegistrations) {
		schema = CatalogSnapshotSchemaV10
	}
	if inputHasConnectorRuntimeFacts(input.MutationDefinitions) {
		schema = CatalogSnapshotSchemaV11
	}
	if inputHasConnectorFinalizationAuthority(input.MutationDefinitions) {
		schema = CatalogSnapshotSchemaV12
	}
	if inputHasConnectorRecoveryEligibility(input.MutationDefinitions) {
		schema = CatalogSnapshotSchemaV13
	}
	if len(input.ApprovalDefinitions) != 0 || inputHasApprovalRuntimeProjection(input.Projections) {
		schema = CatalogSnapshotSchemaV14
	}
	if len(input.FormulaFunctionBundles) != 0 || len(input.OutboundIntentRoutes) != 0 {
		schema = CatalogSnapshotSchemaV15
	}
	formulaBundleValues := make([]formula.FunctionBundle, 0, len(input.FormulaFunctionBundles))
	for i, source := range input.FormulaFunctionBundles {
		path := fmt.Sprintf("formulaFunctionBundles[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		formulaBundleValues = append(formulaBundleValues, source.Bundle)
	}
	formulaBundles, err := canonicalFormulaFunctionBundles(formulaBundleValues)
	if err != nil {
		return Artifact{}, conflict(ConflictInvalidDefinition, "formulaFunctionBundles", "%v", err)
	}
	formulaEngine, err := newRevisionFormulaEngine(formulaBundles)
	if err != nil {
		return Artifact{}, conflict(ConflictInvalidDefinition, "formulaFunctionBundles", "%v", err)
	}
	outboundIntentRoutes, err := canonicalOutboundIntentRoutes(
		input.Lineage.CatalogRevision,
		input.OutboundIntentRoutes,
	)
	if err != nil {
		return Artifact{}, err
	}
	if outboundIntentRoutesUseMailAuthority(outboundIntentRoutes) {
		schema = CatalogSnapshotSchemaV16
	}
	if pageLayoutResolutionSelected {
		schema = CatalogSnapshotSchemaV17
	}
	if inputHasFieldMappingValuePolicies(input.MutationDefinitions) {
		schema = CatalogSnapshotSchemaV18
	}
	if outboundIntentRoutesUseMailV2Authority(outboundIntentRoutes) {
		schema = CatalogSnapshotSchemaV19
	}
	if layoutAuthoringSelected {
		schema = CatalogSnapshotSchemaV21
	}
	doc := catalogDocument{
		Schema: schema, OrgID: input.OrgID, Lineage: input.Lineage,
		Objects: []catalogObject{}, Relationships: []relationshipRecord{},
		SharingRules: []sharingRuleRecord{}, Definitions: []definitionRecord{},
		Projections: []projectionRecord{}, FormulaFunctionBundles: formulaBundles,
		OutboundIntentRoutes: outboundIntentRoutes,
	}

	objectsByName := make(map[string]*models.ObjectMetadata, len(input.Objects))
	objectsByKeyPrefix := make(map[string]string, len(input.Objects))
	for i, source := range input.Objects {
		path := fmt.Sprintf("objects[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		object, effective, err := buildCatalogObject(path, source.Metadata)
		if err != nil {
			return Artifact{}, err
		}
		key := foldIdentity(effective.APIName)
		if _, exists := objectsByName[key]; exists {
			return Artifact{}, conflict(ConflictDuplicateObject, path+".metadata.name", "object %q is duplicated case-insensitively", effective.APIName)
		}
		if err := registerKeyPrefixOwner(objectsByKeyPrefix, effective.KeyPrefix, effective.APIName); err != nil {
			return Artifact{}, conflict(ConflictInvalidIdentity, path+".metadata.keyPrefix", "%v", err)
		}
		objectsByName[key] = effective
		doc.Objects = append(doc.Objects, object)
	}
	sort.Slice(doc.Objects, func(i, j int) bool {
		return identityLess(doc.Objects[i].Metadata.APIName, doc.Objects[j].Metadata.APIName)
	})

	sharingParentByChild := make(map[string]string)
	seenRelationships := make(map[string]struct{}, len(input.Relationships))
	for i, source := range input.Relationships {
		path := fmt.Sprintf("relationships[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		record, err := buildRelationship(path, source.Metadata, objectsByName)
		if err != nil {
			return Artifact{}, err
		}
		key := relationshipKey(record)
		if _, exists := seenRelationships[key]; exists {
			return Artifact{}, conflict(ConflictDuplicateRelationship, path, "relationship %q is duplicated", key)
		}
		seenRelationships[key] = struct{}{}
		if record.IsSharingParent {
			childKey := foldIdentity(record.ChildObjectAPIName)
			if previous, exists := sharingParentByChild[childKey]; exists {
				return Artifact{}, conflict(ConflictAmbiguousSharingParent, path, "child %q has sharing parents %q and %q", record.ChildObjectAPIName, previous, record.ParentObjectAPIName)
			}
			sharingParentByChild[childKey] = record.ParentObjectAPIName
		}
		doc.Relationships = append(doc.Relationships, record)
	}
	// A parent-controlled declaration without a sharing-parent edge is an
	// existing fail-closed metadata state, not an edge the compiler may invent.
	// Preserve it so one malformed object does not make the complete App snapshot
	// unavailable; Runtime permission evaluation denies that object locally.
	sort.Slice(doc.Relationships, func(i, j int) bool {
		return relationshipKey(doc.Relationships[i]) < relationshipKey(doc.Relationships[j])
	})

	seenRules := make(map[string]struct{}, len(input.SharingRules))
	for i, source := range input.SharingRules {
		path := fmt.Sprintf("sharingRules[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		rule, err := buildSharingRule(path, source.Metadata, objectsByName)
		if err != nil {
			return Artifact{}, err
		}
		key := foldIdentity(rule.ID)
		if _, exists := seenRules[key]; exists {
			return Artifact{}, conflict(ConflictInvalidSharingRule, path+".id", "sharing rule %q is duplicated", rule.ID)
		}
		seenRules[key] = struct{}{}
		doc.SharingRules = append(doc.SharingRules, rule)
	}
	sort.Slice(doc.SharingRules, func(i, j int) bool {
		left := foldIdentity(doc.SharingRules[i].ObjectAPIName) + "\x00" + foldIdentity(doc.SharingRules[i].Name) + "\x00" + foldIdentity(doc.SharingRules[i].ID)
		right := foldIdentity(doc.SharingRules[j].ObjectAPIName) + "\x00" + foldIdentity(doc.SharingRules[j].Name) + "\x00" + foldIdentity(doc.SharingRules[j].ID)
		return left < right
	})

	definitionsByID := make(map[string]struct{}, len(input.Definitions))
	for i, source := range input.Definitions {
		path := fmt.Sprintf("definitions[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		if err := requireCanonicalIdentity(path+".componentId", source.ComponentID); err != nil {
			return Artifact{}, err
		}
		if strings.TrimSpace(string(source.DefinitionVersion)) == "" || strings.TrimSpace(string(source.DefinitionVersion)) != string(source.DefinitionVersion) {
			return Artifact{}, conflict(ConflictInvalidDefinition, path+".definitionVersion", "definition version must be non-blank and canonical")
		}
		if !validDigest(source.ContentDigest) {
			return Artifact{}, conflict(ConflictInvalidDefinition, path+".contentDigest", "content digest %q is invalid", source.ContentDigest)
		}
		key := foldIdentity(source.ComponentID)
		if _, exists := definitionsByID[key]; exists {
			return Artifact{}, conflict(ConflictInvalidDefinition, path+".componentId", "definition %q is duplicated", source.ComponentID)
		}
		definitionsByID[key] = struct{}{}
		doc.Definitions = append(doc.Definitions, definitionRecord{
			ComponentID: source.ComponentID, DefinitionVersion: source.DefinitionVersion, ContentDigest: source.ContentDigest,
		})
	}
	sort.Slice(doc.Definitions, func(i, j int) bool {
		return identityLess(doc.Definitions[i].ComponentID, doc.Definitions[j].ComponentID)
	})
	reports, err := buildReportDefinitions(input.Lineage.CatalogRevision, input.ReportDefinitions, objectsByName)
	if err != nil {
		return Artifact{}, err
	}
	for _, report := range reports {
		key := foldIdentity(report.ComponentID)
		if _, exists := definitionsByID[key]; exists {
			return Artifact{}, conflict(ConflictInvalidDefinition, "reportDefinitions", "report component %q duplicates a definition", report.ComponentID)
		}
		definitionsByID[key] = struct{}{}
		doc.Definitions = append(doc.Definitions, definitionRecord{
			ComponentID: report.ComponentID, DefinitionVersion: report.DefinitionVersion, ContentDigest: report.ContentDigest,
		})
	}
	sort.Slice(doc.Definitions, func(i, j int) bool {
		return identityLess(doc.Definitions[i].ComponentID, doc.Definitions[j].ComponentID)
	})
	doc.Reports = reports
	commandRegistrations, err := buildCommandRegistrations(input.Lineage.CatalogRevision, input.CommandRegistrations)
	if err != nil {
		return Artifact{}, err
	}
	for _, registration := range commandRegistrations {
		key := foldIdentity(registration.ComponentID)
		if _, exists := definitionsByID[key]; exists {
			return Artifact{}, conflict(ConflictInvalidDefinition, "commandRegistrations", "command registration component %q duplicates a definition", registration.ComponentID)
		}
		definitionsByID[key] = struct{}{}
		doc.Definitions = append(doc.Definitions, definitionRecord{
			ComponentID: registration.ComponentID, DefinitionVersion: registration.DefinitionVersion,
			ContentDigest: registration.ContentDigest,
		})
	}
	sort.Slice(doc.Definitions, func(i, j int) bool {
		return identityLess(doc.Definitions[i].ComponentID, doc.Definitions[j].ComponentID)
	})
	doc.CommandRegistrations = commandRegistrations
	approvalDefinitions, err := buildApprovalDefinitions(
		input.Lineage.CatalogRevision,
		input.ApprovalDefinitions,
		objectsByName,
	)
	if err != nil {
		return Artifact{}, err
	}
	for _, definition := range approvalDefinitions {
		key := foldIdentity(definition.DefinitionID)
		if _, exists := definitionsByID[key]; exists {
			return Artifact{}, conflict(
				ConflictInvalidDefinition,
				"approvalDefinitions",
				"approval definition %q duplicates another definition component",
				definition.DefinitionID,
			)
		}
		definitionsByID[key] = struct{}{}
		doc.Definitions = append(doc.Definitions, definitionRecord{
			ComponentID:       definition.DefinitionID,
			DefinitionVersion: DefinitionVersion(definition.SourceDigest),
			ContentDigest:     definition.ContentDigest,
		})
	}
	sort.Slice(doc.Definitions, func(i, j int) bool {
		return identityLess(doc.Definitions[i].ComponentID, doc.Definitions[j].ComponentID)
	})
	doc.ApprovalDefinitions = approvalDefinitions
	mutation, err := buildMutationDefinitionDocument(
		input.Lineage.CatalogRevision,
		input.MutationDefinitions,
		objectsByName,
		formulaEngine,
	)
	if err != nil {
		return Artifact{}, err
	}
	// A mutation capability with no validation/Flow/autonumber/etc. rows is a
	// valid, explicit empty metadata closure. Keep it distinct from a snapshot
	// that never selected mutation semantics; Runtime must not infer authority
	// from the absence of rows.
	if mutation == nil && (inputHasMutationProjection(input.Projections) ||
		inputHasEffectivePermissionProjection(input.Projections)) {
		mutation = emptyMutationDefinitionDocument()
	}
	doc.Mutation = mutation
	if err := validateOutboundIntentRoutes(doc.OutboundIntentRoutes, doc.Mutation); err != nil {
		return Artifact{}, err
	}
	if err := validateOutboundMailCompletionMetadata(doc.OutboundIntentRoutes, objectsByName); err != nil {
		return Artifact{}, conflict(ConflictInvalidDefinition, "outboundIntentRoutes", "%v", err)
	}
	recordView, err := buildRecordViewDefinitionDocument(input.Lineage.CatalogRevision, input.AttachmentSourceResolvers, input.AttachmentSourceFields, objectsByName)
	if err != nil {
		return Artifact{}, err
	}
	// As with mutation, an explicit record-view projection owns a closed empty
	// resolver set when this AppRelease defines no decorated source fields.
	if recordView == nil && inputHasRecordViewProjection(input.Projections) {
		recordView = &recordViewDefinitionDocument{
			Resolvers: []recordAttachmentSourceResolverRecord{}, Fields: []recordAttachmentSourceFieldRecord{},
		}
	}
	doc.RecordView = recordView
	var pageLayoutResolution *pageLayoutResolutionDocument
	if pageLayoutResolutionSelected {
		pageLayoutResolution, err = buildPageLayoutResolutionDocument(
			input.Lineage.CatalogRevision,
			input.PageLayouts,
			input.LayoutAssignments,
			input.PageLayoutRecordTypes,
			input.RelatedListResolutions,
			objectsByName,
		)
		if err != nil {
			return Artifact{}, err
		}
		if pageLayoutResolution == nil {
			return Artifact{}, conflict(ConflictInvalidProjection, "pageLayoutResolution", "page-layout resolution projection has no routing closure")
		}
		if err := validatePageLayoutResolutionDocument(pageLayoutResolution, objectsByName, CatalogSnapshotSchemaV17); err != nil {
			return Artifact{}, conflict(ConflictInvalidDefinition, "pageLayoutResolution", "%v", err)
		}
	}
	doc.PageLayoutResolution = pageLayoutResolution
	if layoutAuthoringSelected {
		layoutAuthoring, err := buildLayoutAuthoringDocumentFromAggregates(
			input.Lineage.CatalogRevision, input.LayoutAuthoringAggregates, objectsByName, mutation,
		)
		if err != nil {
			return Artifact{}, err
		}
		doc.LayoutAuthoring = layoutAuthoring
	}

	seenProjections := make(map[string]struct{}, len(input.Projections))
	for i, source := range input.Projections {
		path := fmt.Sprintf("runtimeProjections[%d]", i)
		if err := requireSourceRevision(path, input.Lineage.CatalogRevision, source.CatalogRevision); err != nil {
			return Artifact{}, err
		}
		if err := requireCanonicalIdentity(path+".appInstallationId", source.AppInstallationID); err != nil {
			return Artifact{}, err
		}
		if err := requireCanonicalIdentity(path+".capabilityId", source.CapabilityID); err != nil {
			return Artifact{}, err
		}
		if err := validateProjectionProfile(path+".profile", source.Profile); err != nil {
			return Artifact{}, err
		}
		if source.Profile == ProjectionLayoutAuthoringV1 {
			if err := validateLayoutAuthoringProjectionBinding(source.CapabilityID, source.DefinitionComponentIDs); err != nil {
				return Artifact{}, conflict(ConflictInvalidProjection, path, "%v", err)
			}
		}
		projectionKey := foldIdentity(source.AppInstallationID) + "\x00" + foldIdentity(source.CapabilityID)
		if _, exists := seenProjections[projectionKey]; exists {
			return Artifact{}, conflict(ConflictInvalidProjection, path, "projection for installation/capability is duplicated")
		}
		seenProjections[projectionKey] = struct{}{}

		objectNames, err := canonicalIdentitySet(path+".objectApiNames", source.ObjectAPINames)
		if err != nil {
			return Artifact{}, err
		}
		if len(objectNames) == 0 && source.Profile != ProjectionCommandAcceptanceV1 {
			return Artifact{}, conflict(ConflictInvalidProjection, path+".objectApiNames", "projection must contain at least one object")
		}
		if len(objectNames) != 0 && source.Profile == ProjectionCommandAcceptanceV1 {
			return Artifact{}, conflict(ConflictInvalidProjection, path+".objectApiNames", "command-acceptance projection must not contain object metadata")
		}
		for _, name := range objectNames {
			if _, exists := objectsByName[foldIdentity(name)]; !exists {
				return Artifact{}, conflict(ConflictInvalidProjection, path+".objectApiNames", "object %q does not exist in this revision", name)
			}
		}
		if err := validateProjectionObjectClosure(path+".objectApiNames", source.Profile, objectNames, objectsByName); err != nil {
			return Artifact{}, err
		}
		projectedObjects := make([]runtimeObjectRecord, 0, len(objectNames))
		includedObjects := make(map[string]struct{}, len(objectNames))
		for _, name := range objectNames {
			includedObjects[foldIdentity(name)] = struct{}{}
			if source.Profile == ProjectionPageLayoutResolutionV1 || source.Profile == ProjectionLayoutAuthoringV1 {
				continue
			}
			record, err := buildRuntimeObject(
				objectsByName[foldIdentity(name)], source.Profile, objectsByName,
				runtimeViewSchemaFor(doc.Schema, source.Profile),
			)
			if err != nil {
				return Artifact{}, err
			}
			projectedObjects = append(projectedObjects, record)
		}
		if source.Profile == ProjectionEffectivePermissionsV1 {
			if _, err := projectEffectivePermissionDefinitions(
				mutation, doc.Objects, includedObjects,
			); err != nil {
				return Artifact{}, err
			}
		}
		if err := validateRuntimePolicyDependencies(projectedObjects, includedObjects); err != nil {
			return Artifact{}, err
		}
		definitionIDs, err := canonicalIdentitySet(path+".definitionComponentIds", source.DefinitionComponentIDs)
		if err != nil {
			return Artifact{}, err
		}
		if source.Profile == ProjectionEffectivePermissionsV1 && len(definitionIDs) != 0 {
			return Artifact{}, conflict(
				ConflictInvalidProjection,
				path+".definitionComponentIds",
				"effective-permissions projection must not contain executable definitions",
			)
		}
		for _, id := range definitionIDs {
			if _, exists := definitionsByID[foldIdentity(id)]; !exists {
				return Artifact{}, conflict(ConflictInvalidProjection, path+".definitionComponentIds", "definition %q does not exist in this revision", id)
			}
		}
		if source.Profile == ProjectionReportExecutionV1 {
			reportComponents := make(map[string]struct{}, len(reports))
			for _, report := range reports {
				reportComponents[foldIdentity(report.ComponentID)] = struct{}{}
			}
			for _, id := range definitionIDs {
				if _, exists := reportComponents[foldIdentity(id)]; !exists {
					return Artifact{}, conflict(ConflictInvalidProjection, path+".definitionComponentIds", "report-execution definition %q is not a report component", id)
				}
			}
		}
		commandComponents := make(map[string]struct{}, len(commandRegistrations))
		for _, registration := range commandRegistrations {
			commandComponents[foldIdentity(registration.ComponentID)] = struct{}{}
		}
		if source.Profile == ProjectionCommandAcceptanceV1 {
			if len(definitionIDs) == 0 {
				return Artifact{}, conflict(ConflictInvalidProjection, path+".definitionComponentIds", "command-acceptance projection must select at least one command registration")
			}
			for _, id := range definitionIDs {
				if _, exists := commandComponents[foldIdentity(id)]; !exists {
					return Artifact{}, conflict(ConflictInvalidProjection, path+".definitionComponentIds", "command-acceptance definition %q is not a command registration", id)
				}
			}
		}
		approvalComponents := make(map[string]approvalDefinitionRecord, len(approvalDefinitions))
		for _, definition := range approvalDefinitions {
			approvalComponents[foldIdentity(definition.DefinitionID)] = definition
		}
		if profileUsesApprovalAuthority(source.Profile) {
			for _, id := range definitionIDs {
				definition, exists := approvalComponents[foldIdentity(id)]
				if !exists {
					return Artifact{}, conflict(
						ConflictInvalidProjection,
						path+".definitionComponentIds",
						"approval projection definition %q is not an approval definition",
						id,
					)
				}
				if _, exists := includedObjects[foldIdentity(definition.ObjectAPIName)]; !exists {
					return Artifact{}, conflict(
						ConflictInvalidProjection,
						path+".definitionComponentIds",
						"approval projection definition %q references object %q outside the projection closure",
						id,
						definition.ObjectAPIName,
					)
				}
			}
		}
		if source.Profile != ProjectionCommandAcceptanceV1 {
			for _, id := range definitionIDs {
				if _, exists := commandComponents[foldIdentity(id)]; exists {
					return Artifact{}, conflict(ConflictInvalidProjection, path+".definitionComponentIds", "profile %q cannot select command registration %q", source.Profile, id)
				}
			}
		}
		if !profileUsesApprovalAuthority(source.Profile) {
			for _, id := range definitionIDs {
				if _, exists := approvalComponents[foldIdentity(id)]; exists {
					return Artifact{}, conflict(
						ConflictInvalidProjection,
						path+".definitionComponentIds",
						"profile %q cannot select approval definition %q",
						source.Profile,
						id,
					)
				}
			}
		}
		if profileUsesMutationClosure(source.Profile) &&
			!(source.Profile == ProjectionApprovalExecutionV1 && len(definitionIDs) == 0) {
			selected := make(map[string]struct{}, len(definitionIDs))
			for _, id := range definitionIDs {
				selected[foldIdentity(id)] = struct{}{}
			}
			projectedMutation, err := projectMutationDefinitions(
				mutation,
				includedObjects,
				source.Profile,
				source.CapabilityID,
				approvalFlowNames(selectedApprovalDefinitions(approvalDefinitions, selected)),
				outboundIntentRoutes,
				formulaEngine,
			)
			if err != nil {
				return Artifact{}, err
			}
			if err := applyRuntimeMutationDerivedObjectFacts(mutation, projectedMutation, projectedObjects); err != nil {
				return Artifact{}, conflict(ConflictInvalidProjection, path, "%v", err)
			}
		}
		if source.Profile == ProjectionRecordViewV1 && recordView == nil {
			return Artifact{}, conflict(ConflictInvalidProjection, path, "record-view projection has no resolver closure")
		}
		doc.Projections = append(doc.Projections, projectionRecord{
			AppInstallationID: source.AppInstallationID, CapabilityID: source.CapabilityID,
			Profile:        source.Profile,
			ObjectAPINames: objectNames, DefinitionComponentIDs: definitionIDs,
		})
	}
	sort.Slice(doc.Projections, func(i, j int) bool {
		left := doc.Projections[i].AppInstallationID + "\x00" + doc.Projections[i].CapabilityID
		right := doc.Projections[j].AppInstallationID + "\x00" + doc.Projections[j].CapabilityID
		return identityLess(left, right)
	})
	data, err := marshalCanonical(doc)
	if err != nil {
		return Artifact{}, conflict(ConflictEncoding, "catalogSnapshot", "encode canonical artifact: %v", err)
	}
	return makeArtifact(ArtifactCatalogSnapshot, data), nil
}

func ProjectRuntimeMetadataView(snapshot *CatalogSnapshot, key ProjectionKey) (Artifact, error) {
	if snapshot == nil {
		return Artifact{}, conflict(ConflictInvalidProjection, "snapshot", "catalog snapshot is required")
	}
	if err := requireCanonicalIdentity("projection.appInstallationId", key.AppInstallationID); err != nil {
		return Artifact{}, err
	}
	if err := requireCanonicalIdentity("projection.capabilityId", key.CapabilityID); err != nil {
		return Artifact{}, err
	}

	var projection *projectionRecord
	for i := range snapshot.doc.Projections {
		candidate := &snapshot.doc.Projections[i]
		if candidate.AppInstallationID == key.AppInstallationID && candidate.CapabilityID == key.CapabilityID {
			projection = candidate
			break
		}
	}
	if projection == nil {
		return Artifact{}, conflict(ConflictInvalidProjection, "projection", "installation/capability scope is absent")
	}
	formulaEngine, err := formulaEngineForCatalogArtifact(snapshot.doc.Schema, snapshot.doc.FormulaFunctionBundles)
	if err != nil {
		return Artifact{}, conflict(ConflictInvalidProjection, "formulaFunctionBundles", "%v", err)
	}
	emptyApprovalExecution := projection.Profile == ProjectionApprovalExecutionV1 &&
		len(projection.DefinitionComponentIDs) == 0
	effectiveProfile := projection.Profile
	if emptyApprovalExecution {
		// An installed App may expose the neutral Approval capability before this
		// immutable revision contains an Approval definition. Preserve the
		// capability binding, but project the object/policy surface as read-only
		// and carry no mutation closure. Runtime rejects execution before opening
		// a transaction, so absence cannot become ambient write authority.
		effectiveProfile = ProjectionApprovalReadV1
	}

	included := make(map[string]struct{}, len(projection.ObjectAPINames))
	for _, name := range projection.ObjectAPINames {
		included[foldIdentity(name)] = struct{}{}
	}
	doc := runtimeViewDocument{
		Schema: runtimeViewSchemaFor(snapshot.doc.Schema, projection.Profile), SourceSnapshotDigest: snapshot.digest,
		OrgID: snapshot.doc.OrgID, AppInstallationID: projection.AppInstallationID,
		CapabilityID: projection.CapabilityID, Profile: projection.Profile, Lineage: snapshot.doc.Lineage,
		Objects: []runtimeObjectRecord{}, Relationships: []runtimeRelationshipRecord{},
		SharingRules: []runtimeSharingRuleRecord{}, Definitions: []definitionRecord{},
	}
	objectsByName := make(map[string]*models.ObjectMetadata, len(snapshot.doc.Objects))
	for i := range snapshot.doc.Objects {
		object, err := reconstructCatalogObject(snapshot.doc.Objects[i])
		if err != nil {
			return Artifact{}, conflict(ConflictEncoding, "objects", "reconstruct catalog object: %v", err)
		}
		objectsByName[foldIdentity(object.APIName)] = object
	}
	for i := range snapshot.doc.Objects {
		object := objectsByName[foldIdentity(snapshot.doc.Objects[i].Metadata.APIName)]
		if _, exists := included[foldIdentity(object.APIName)]; !exists {
			continue
		}
		if projection.Profile == ProjectionPageLayoutResolutionV1 || projection.Profile == ProjectionLayoutAuthoringV1 {
			continue
		}
		record, err := buildRuntimeObject(object, effectiveProfile, objectsByName, doc.Schema)
		if err != nil {
			return Artifact{}, err
		}
		doc.Objects = append(doc.Objects, record)
	}
	if profileUsesMutationClosure(effectiveProfile) {
		if err := validateRuntimeAssignmentPolicyEligibilityProjection(doc.Objects); err != nil {
			return Artifact{}, err
		}
	}

	for _, relationship := range snapshot.doc.Relationships {
		if projection.Profile == ProjectionPageLayoutResolutionV1 || projection.Profile == ProjectionLayoutAuthoringV1 {
			continue
		}
		if effectiveProfile != ProjectionRecordQueryV1 && !profileUsesMutationClosure(effectiveProfile) && !relationship.IsSharingParent {
			continue
		}
		_, childIncluded := included[foldIdentity(relationship.ChildObjectAPIName)]
		_, parentIncluded := included[foldIdentity(relationship.ParentObjectAPIName)]
		if childIncluded != parentIncluded {
			return Artifact{}, conflict(ConflictInvalidProjection, "relationships", "scope includes one endpoint of relationship %q -> %q", relationship.ChildObjectAPIName, relationship.ParentObjectAPIName)
		}
		if childIncluded && parentIncluded {
			record := runtimeRelationshipRecord{
				ChildObjectAPIName:  relationship.ChildObjectAPIName,
				ParentObjectAPIName: relationship.ParentObjectAPIName,
				FieldAPIName:        relationship.FieldAPIName,
				RelationshipType:    relationship.RelationshipType,
				IsSharingParent:     relationship.IsSharingParent,
			}
			if effectiveProfile == ProjectionRecordQueryV1 {
				record.RelationshipName = relationship.RelationshipName
			}
			if profileUsesMutationClosure(effectiveProfile) {
				record.RelationshipName = relationship.RelationshipName
				record.Mutation = &runtimeMutationRelationshipRecord{
					CascadeDelete: relationship.CascadeDelete, RestrictedDelete: relationship.RestrictedDelete,
				}
			}
			doc.Relationships = append(doc.Relationships, record)
		}
	}
	sort.Slice(doc.Relationships, func(i, j int) bool {
		return runtimeRelationshipKey(doc.Relationships[i]) < runtimeRelationshipKey(doc.Relationships[j])
	})
	for _, rule := range snapshot.doc.SharingRules {
		if projection.Profile == ProjectionEffectivePermissionsV1 || projection.Profile == ProjectionPageLayoutResolutionV1 || projection.Profile == ProjectionLayoutAuthoringV1 {
			continue
		}
		if _, exists := included[foldIdentity(rule.ObjectAPIName)]; exists {
			doc.SharingRules = append(doc.SharingRules, runtimeSharingRuleRecord{
				ObjectAPIName: rule.ObjectAPIName, Criteria: rule.Criteria,
				AccessLevel: rule.AccessLevel, SharedFromRoleID: rule.SharedFromRoleID,
				SharedFromGroupID: rule.SharedFromGroupID, ShareWithRoleID: rule.ShareWithRoleID,
				ShareWithGroupID: rule.ShareWithGroupID,
			})
		}
	}
	sort.Slice(doc.SharingRules, func(i, j int) bool {
		return runtimeSharingRuleKey(doc.SharingRules[i]) < runtimeSharingRuleKey(doc.SharingRules[j])
	})
	definitionSet := make(map[string]struct{}, len(projection.DefinitionComponentIDs))
	for _, id := range projection.DefinitionComponentIDs {
		definitionSet[foldIdentity(id)] = struct{}{}
	}
	if snapshot.doc.Schema == CatalogSnapshotSchemaV3 && projection.Profile == ProjectionRecordMutationV1 {
		// V3 used this set as mutation behavior-selection authority. Its
		// components cannot be distinguished from synthetic capability tuples,
		// so migration must not propagate them into a v4 Runtime view.
		definitionSet = map[string]struct{}{}
	}
	for _, definition := range snapshot.doc.Definitions {
		if _, exists := definitionSet[foldIdentity(definition.ComponentID)]; exists {
			doc.Definitions = append(doc.Definitions, definition)
		}
	}
	if projection.Profile == ProjectionReportExecutionV1 {
		for _, report := range snapshot.doc.Reports {
			if _, exists := definitionSet[foldIdentity(report.ComponentID)]; exists {
				doc.Reports = append(doc.Reports, report)
			}
		}
	}
	if projection.Profile == ProjectionCommandAcceptanceV1 {
		for _, registration := range snapshot.doc.CommandRegistrations {
			if _, exists := definitionSet[foldIdentity(registration.ComponentID)]; exists {
				doc.CommandRegistrations = append(doc.CommandRegistrations, registration)
			}
		}
		if projection.Profile == ProjectionCommandAcceptanceV1 && len(doc.CommandRegistrations) == 0 {
			return Artifact{}, conflict(ConflictInvalidProjection, "commandRegistrations", "command-acceptance projection contains no selected registrations")
		}
		if commandRecordsHaveExecutionBindings(doc.CommandRegistrations) {
			doc.Schema = RuntimeMetadataViewSchemaV12
		}
	}
	if profileUsesApprovalAuthority(projection.Profile) {
		for _, definition := range snapshot.doc.ApprovalDefinitions {
			if _, exists := definitionSet[foldIdentity(definition.DefinitionID)]; exists {
				doc.ApprovalDefinitions = append(doc.ApprovalDefinitions, definition)
			}
		}
		doc.Schema = RuntimeMetadataViewSchemaV16
	}
	if projection.Profile == ProjectionRecordQueryV1 {
		_, hasDefinitions := included[foldIdentity("ProcessDefinition")]
		_, hasNodes := included[foldIdentity("ProcessNode")]
		if hasDefinitions && hasNodes {
			for _, definition := range snapshot.doc.ApprovalDefinitions {
				if _, objectIncluded := included[foldIdentity(definition.ObjectAPIName)]; !objectIncluded {
					continue
				}
				doc.ReferenceIdentities = append(doc.ReferenceIdentities, runtimeReferenceIdentityRecord{
					ObjectAPIName: "ProcessDefinition", ID: definition.DefinitionID,
					Name:          referenceIdentityDisplayName(definition.Definition.Label, definition.DeveloperName),
					DeveloperName: definition.DeveloperName,
				})
				for index, step := range definition.Definition.Steps {
					doc.ReferenceIdentities = append(doc.ReferenceIdentities, runtimeReferenceIdentityRecord{
						ObjectAPIName: "ProcessNode",
						ID:            ApprovalNodeStorageID(definition.DefinitionID, step.Name),
						// ProcessNode.Name is a stable public identity in the legacy
						// external surface, not the mutable display label.
						Name: step.Name, DeveloperName: step.Name,
						ProcessDefinitionID: definition.DefinitionID, StepOrder: index + 1,
					})
				}
			}
		}
		if len(doc.ReferenceIdentities) != 0 {
			sort.Slice(doc.ReferenceIdentities, func(i, j int) bool {
				left, right := doc.ReferenceIdentities[i], doc.ReferenceIdentities[j]
				return foldIdentity(left.ObjectAPIName)+"\x00"+left.ID < foldIdentity(right.ObjectAPIName)+"\x00"+right.ID
			})
			doc.Schema = RuntimeMetadataViewSchemaV20
		}
	}
	if profileUsesMutationClosure(projection.Profile) && !emptyApprovalExecution {
		mutation, err := projectMutationDefinitions(
			snapshot.doc.Mutation,
			included,
			projection.Profile,
			projection.CapabilityID,
			approvalFlowNames(doc.ApprovalDefinitions),
			snapshot.doc.OutboundIntentRoutes,
			formulaEngine,
		)
		if err != nil {
			return Artifact{}, err
		}
		doc.Mutation = mutation
		if projection.Profile == ProjectionRecordMutationV1 {
			doc.Schema = runtimeMutationViewSchemaFor(snapshot.doc.Schema, mutation)
		}
		if projection.Profile == ProjectionRecordMutationV1 && len(snapshot.doc.FormulaFunctionBundles) != 0 {
			doc.FormulaFunctionBundles = formula.CloneFunctionBundles(snapshot.doc.FormulaFunctionBundles)
			doc.Schema = RuntimeMetadataViewSchemaV17
		}
		if projection.Profile == ProjectionApprovalExecutionV1 && len(snapshot.doc.FormulaFunctionBundles) != 0 {
			doc.FormulaFunctionBundles = formula.CloneFunctionBundles(snapshot.doc.FormulaFunctionBundles)
			doc.Schema = RuntimeMetadataViewSchemaV19
		}
		if projection.Profile == ProjectionRecordMutationV1 && len(snapshot.doc.OutboundIntentRoutes) != 0 {
			doc.OutboundIntentRoutes, err = cloneJSON(snapshot.doc.OutboundIntentRoutes)
			if err != nil {
				return Artifact{}, conflict(ConflictEncoding, "outboundIntentRoutes", "clone Runtime routes: %v", err)
			}
			if outboundIntentRoutesUseMailV2Authority(doc.OutboundIntentRoutes) {
				doc.Schema = RuntimeMetadataViewSchemaV26
			} else if outboundIntentRoutesUseMailAuthority(doc.OutboundIntentRoutes) {
				doc.Schema = RuntimeMetadataViewSchemaV22
			} else {
				doc.Schema = RuntimeMetadataViewSchemaV17
			}
		}
		// V25 is the complete mutation grammar for executable mapping policies.
		// V26 is its outbound-mail V2 superset and must remain selected when both
		// authorities share the record-mutation projection. Approval execution
		// projects the same mapping facts, while approval-read remains v16.
		if mutationHasFieldMappingValuePolicies(mutation) && doc.Schema != RuntimeMetadataViewSchemaV26 {
			doc.Schema = RuntimeMetadataViewSchemaV25
		}
		if err := applyRuntimeMutationDerivedObjectFacts(snapshot.doc.Mutation, mutation, doc.Objects); err != nil {
			return Artifact{}, conflict(ConflictInvalidProjection, "mutation", "%v", err)
		}
	}
	if projection.Profile == ProjectionEffectivePermissionsV1 {
		doc.EffectivePermissions, err = projectEffectivePermissionDefinitions(
			snapshot.doc.Mutation, snapshot.doc.Objects, included,
		)
		if err != nil {
			return Artifact{}, err
		}
		doc.Schema = RuntimeMetadataViewSchemaV21
	}
	if projection.Profile == ProjectionRecordViewV1 {
		doc.RecordView = projectRecordViewDefinitions(snapshot.doc.RecordView, included)
	}
	if projection.Profile == ProjectionPageLayoutResolutionV1 {
		doc.PageLayoutResolution = projectPageLayoutResolution(snapshot.doc.PageLayoutResolution, included)
		doc.Schema = RuntimeMetadataViewSchemaV23
	}
	if projection.Profile == ProjectionLayoutAuthoringV1 {
		doc.LayoutAuthoring = projectLayoutAuthoring(snapshot.doc.LayoutAuthoring, included)
		doc.Schema = runtimeViewSchemaFor(snapshot.doc.Schema, ProjectionLayoutAuthoringV1)
	}
	if projection.Profile != ProjectionEffectivePermissionsV1 {
		if err := validateRuntimePolicyDependencies(doc.Objects, included); err != nil {
			return Artifact{}, err
		}
	}
	if err := validateOutboundMailCompletionMetadata(doc.OutboundIntentRoutes, runtimeObjectsToModels(doc.Objects)); err != nil {
		return Artifact{}, conflict(ConflictInvalidProjection, "outboundIntentRoutes", "%v", err)
	}

	data, err := marshalCanonical(doc)
	if err != nil {
		return Artifact{}, conflict(ConflictEncoding, "runtimeMetadataView", "encode canonical artifact: %v", err)
	}
	return makeArtifact(ArtifactRuntimeMetadataView, data), nil
}

func validateRuntimeAssignmentPolicyEligibilityProjection(objects []runtimeObjectRecord) error {
	authorities := 0
	populations := 0
	for i := range objects {
		policy := objects[i].AccessPolicy
		if policy == nil || policy.Lifecycle == nil || policy.Lifecycle.AssignmentPolicyEligibility == nil {
			continue
		}
		switch policy.Lifecycle.AssignmentPolicyEligibility.Role {
		case models.AssignmentPolicyEligibilitySelectorAuthority:
			authorities++
		case models.AssignmentPolicyEligibilitySubjectPopulation:
			populations++
		}
	}
	if authorities == 0 && populations == 0 {
		return nil
	}
	if authorities != 1 || populations == 0 {
		return conflict(
			ConflictInvalidProjection,
			"objects.accessPolicy.lifecycle.assignmentPolicyEligibility",
			"assignment-policy mutation projection requires exactly one selector authority and at least one subject population; got authorities=%d populations=%d",
			authorities,
			populations,
		)
	}
	return nil
}

func buildCatalogObject(path string, source *models.ObjectMetadata) (catalogObject, *models.ObjectMetadata, error) {
	if source == nil {
		return catalogObject{}, nil, conflict(ConflictInvalidIdentity, path, "object metadata is nil")
	}
	if err := requireCatalogObjectAPIName(path+".metadata.name", source.APIName); err != nil {
		return catalogObject{}, nil, err
	}

	base, err := cloneJSON(*source)
	if err != nil {
		return catalogObject{}, nil, conflict(ConflictEncoding, path, "clone object metadata: %v", err)
	}
	base.Fields = nil
	base.FieldsMap = nil
	normalizedReadSource, err := models.NormalizeObjectReadSourceConfig(source.ReadSource)
	if err != nil {
		return catalogObject{}, nil, conflict(ConflictInvalidReadSource, path+".metadata.readSource", "%v", err)
	}
	base.ReadSource = normalizedReadSource

	fields := make([]catalogField, 0, len(source.Fields))
	seenFields := make(map[string]struct{}, len(source.Fields))
	for i := range source.Fields {
		fieldPath := fmt.Sprintf("%s.fields[%d]", path, i)
		field := source.Fields[i]
		if err := requireCanonicalIdentity(fieldPath+".name", field.APIName); err != nil {
			return catalogObject{}, nil, err
		}
		fieldKey := foldIdentity(field.APIName)
		if _, exists := seenFields[fieldKey]; exists {
			return catalogObject{}, nil, conflict(ConflictDuplicateField, fieldPath+".name", "field %q is duplicated case-insensitively", field.APIName)
		}
		seenFields[fieldKey] = struct{}{}
		copyField, err := cloneJSON(field)
		if err != nil {
			return catalogObject{}, nil, conflict(ConflictEncoding, fieldPath, "clone field metadata: %v", err)
		}
		copyField.PhysicalName = ""
		copyField.GeneratedForPolymorphicReference = ""
		copyField.ReferenceTo = canonicalLooseSet(copyField.ReferenceTo)
		copyField.WritableReferenceTo = canonicalLooseSet(copyField.WritableReferenceTo)
		fields = append(fields, catalogField{
			Metadata: copyField, PhysicalName: field.PhysicalName,
			GeneratedForPolymorphicReference: field.GeneratedForPolymorphicReference,
		})
	}
	sort.Slice(fields, func(i, j int) bool {
		return identityLess(fields[i].Metadata.APIName, fields[j].Metadata.APIName)
	})
	record := catalogObject{Metadata: base, Fields: fields}
	effective, err := reconstructCatalogObject(record)
	if err != nil {
		return catalogObject{}, nil, conflict(ConflictEncoding, path, "reconstruct effective object: %v", err)
	}
	if _, err := effective.ResolveReadSourceTable(); err != nil {
		return catalogObject{}, nil, conflict(ConflictInvalidReadSource, path+".metadata.readSource", "%v", err)
	}
	return record, effective, nil
}

func reconstructCatalogObject(record catalogObject) (*models.ObjectMetadata, error) {
	object, err := cloneJSON(record.Metadata)
	if err != nil {
		return nil, err
	}
	object.Fields = make([]models.FieldMetadata, len(record.Fields))
	object.FieldsMap = make(map[string]*models.FieldMetadata, len(record.Fields))
	for i := range record.Fields {
		field, err := cloneJSON(record.Fields[i].Metadata)
		if err != nil {
			return nil, err
		}
		field.PhysicalName = record.Fields[i].PhysicalName
		field.GeneratedForPolymorphicReference = record.Fields[i].GeneratedForPolymorphicReference
		object.Fields[i] = field
		object.FieldsMap[foldIdentity(field.APIName)] = &object.Fields[i]
	}
	return &object, nil
}

func buildRelationship(path string, source *models.Relationship, objects map[string]*models.ObjectMetadata) (relationshipRecord, error) {
	if source == nil {
		return relationshipRecord{}, conflict(ConflictInvalidRelationship, path, "relationship metadata is nil")
	}
	for name, value := range map[string]string{
		"childObjectApiName":  source.ChildObjectAPIName,
		"parentObjectApiName": source.ParentObjectAPIName,
		"fieldApiName":        source.FieldAPIName,
	} {
		if err := requireCanonicalIdentity(path+"."+name, value); err != nil {
			return relationshipRecord{}, err
		}
	}
	child := objects[foldIdentity(source.ChildObjectAPIName)]
	if child == nil {
		return relationshipRecord{}, conflict(ConflictInvalidRelationship, path+".childObjectApiName", "child object %q is absent", source.ChildObjectAPIName)
	}
	if objects[foldIdentity(source.ParentObjectAPIName)] == nil {
		return relationshipRecord{}, conflict(ConflictInvalidRelationship, path+".parentObjectApiName", "parent object %q is absent", source.ParentObjectAPIName)
	}
	if models.FindField(child, source.FieldAPIName) == nil {
		return relationshipRecord{}, conflict(ConflictInvalidRelationship, path+".fieldApiName", "field %q is absent from child %q", source.FieldAPIName, source.ChildObjectAPIName)
	}
	return relationshipRecord{
		ID: source.ID, ChildObjectAPIName: source.ChildObjectAPIName,
		ParentObjectAPIName: source.ParentObjectAPIName, FieldAPIName: source.FieldAPIName,
		RelationshipName: source.RelationshipName, RelationshipType: source.RelationshipType,
		IsSharingParent: source.IsSharingParent, CascadeDelete: source.CascadeDelete,
		RestrictedDelete: source.RestrictedDelete, RelatedListLabel: source.RelatedListLabel,
		RelatedListFields: source.RelatedListFields,
	}, nil
}

func buildSharingRule(path string, source *models.SharingRule, objects map[string]*models.ObjectMetadata) (sharingRuleRecord, error) {
	if source == nil {
		return sharingRuleRecord{}, conflict(ConflictInvalidSharingRule, path, "sharing rule metadata is nil")
	}
	if source.IsDeleted {
		return sharingRuleRecord{}, conflict(ConflictInvalidSharingRule, path, "deleted sharing rule is not effective metadata")
	}
	for name, value := range map[string]string{"id": source.ID, "objectApiName": source.ObjectAPIName, "name": source.Name, "accessLevel": source.AccessLevel} {
		if err := requireCanonicalIdentity(path+"."+name, value); err != nil {
			return sharingRuleRecord{}, err
		}
	}
	object := objects[foldIdentity(source.ObjectAPIName)]
	if object == nil {
		return sharingRuleRecord{}, conflict(ConflictInvalidSharingRule, path+".objectApiName", "object %q is absent", source.ObjectAPIName)
	}
	criteria := strings.TrimSpace(source.Criteria)
	if criteria != "" && criteria != "[]" {
		if err := validateSharingRuleCriteria(object, criteria); err != nil {
			return sharingRuleRecord{}, conflict(ConflictInvalidSharingRule, path+".criteria", "criteria validation failed: %v", err)
		}
	}
	sharedFromRoleID, err := canonicalOptionalIdentity(path+".sharedFromRoleId", source.SharedFromRoleID)
	if err != nil {
		return sharingRuleRecord{}, err
	}
	sharedFromGroupID, err := canonicalOptionalIdentity(path+".sharedFromGroupId", source.SharedFromGroupID)
	if err != nil {
		return sharingRuleRecord{}, err
	}
	shareWithRoleID, err := canonicalOptionalIdentity(path+".shareWithRoleId", source.ShareWithRoleID)
	if err != nil {
		return sharingRuleRecord{}, err
	}
	shareWithGroupID, err := canonicalOptionalIdentity(path+".shareWithGroupId", source.ShareWithGroupID)
	if err != nil {
		return sharingRuleRecord{}, err
	}
	return sharingRuleRecord{
		ID: source.ID, ObjectAPIName: source.ObjectAPIName, Name: source.Name,
		Criteria: criteria, AccessLevel: source.AccessLevel,
		SharedFromRoleID: sharedFromRoleID, SharedFromGroupID: sharedFromGroupID,
		ShareWithRoleID: shareWithRoleID, ShareWithGroupID: shareWithGroupID,
	}, nil
}

func validateSharingRuleCriteria(object *models.ObjectMetadata, criteria string) error {
	expression, err := formula.SharingRuleRecordAccessCriteriaExpression(criteria, object)
	if err != nil {
		return err
	}
	return formula.NewEngine().ValidateFilterFormulaExpression(expression, object)
}

func buildRuntimeObject(
	source *models.ObjectMetadata,
	profile ProjectionProfile,
	objectCatalog map[string]*models.ObjectMetadata,
	runtimeSchema string,
) (runtimeObjectRecord, error) {
	if profile == ProjectionEffectivePermissionsV1 {
		return buildEffectivePermissionRuntimeObject(source)
	}
	readTable, err := source.ResolveReadSourceTable()
	if err != nil {
		return runtimeObjectRecord{}, conflict(ConflictInvalidReadSource, "objects."+source.APIName+".readSource", "%v", err)
	}
	accessPolicy, err := projectAccessPolicy(source.AccessPolicy, profile, runtimeSchema)
	if err != nil {
		return runtimeObjectRecord{}, err
	}
	if profileUsesMutationClosure(profile) {
		if err := resolveAssignmentPolicyEligibilityStorage(source, accessPolicy, objectCatalog); err != nil {
			return runtimeObjectRecord{}, err
		}
	}
	record := runtimeObjectRecord{
		APIName: source.APIName, RequiredReadPermission: source.RequiredReadPermission, AccessPolicy: accessPolicy,
		SharingModel: source.SharingModel, EnableHierarchySharing: source.EnableHierarchySharing,
		ReadSource: source.ReadSource, ResolvedBaseReadTable: readTable, Fields: []runtimeFieldRecord{},
	}
	if profile == ProjectionRecordAccessV1 {
		record.KeyPrefix = clonePointer(source.KeyPrefix)
	}
	if profile == ProjectionRecordAccessV1 || recordViewHasAccessDecisionClosure(profile, runtimeSchema) || profileUsesMutationClosure(profile) {
		record.RequiredWritePermission = source.RequiredWritePermission
		record.LockRule = clonePointer(source.LockRule)
		if record.LockRule != nil {
			if err := formula.ValidateLockRuleExpression(*record.LockRule, formula.NewEngine(), source); err != nil {
				return runtimeObjectRecord{}, conflict(ConflictInvalidLockRule, "objects."+source.APIName+".lockRule", "%v", err)
			}
		}
	}
	if profileUsesMutationClosure(profile) {
		mutation, err := buildRuntimeMutationObject(source)
		if err != nil {
			return runtimeObjectRecord{}, err
		}
		record.Mutation = mutation
	}
	for i := range source.Fields {
		field := source.Fields[i]
		var childRelationshipName *string
		if profile == ProjectionRecordQueryV1 {
			childRelationshipName = clonePointer(field.ChildRelationshipName)
		}
		projected := runtimeFieldRecord{
			APIName: field.APIName, PhysicalName: field.PhysicalName,
			Type: field.Type, LogicalType: field.LogicalType, Required: field.Required,
			IsCompound: field.IsCompound, ComposedFrom: append([]string(nil), field.ComposedFrom...),
			ReferenceTo: canonicalLooseSet(field.ReferenceTo), IsPolymorphic: field.IsPolymorphic,
			Formula: field.Formula, ReturnType: field.ReturnType, FormulaMode: field.FormulaMode,
			FormulaStorageMode: field.FormulaStorageMode, ProjectionConfig: field.ProjectionConfig,
			IsMasterDetail: field.IsMasterDetail, RelationshipName: field.RelationshipName,
			ChildRelationshipName: childRelationshipName,
			Precision:             field.Precision, Scale: field.Scale,
			IsVirtual: field.IsVirtual, VirtualKind: field.VirtualKind,
			VirtualSourceAPIName: field.VirtualSourceAPIName, ReadProtectionPolicy: field.ReadProtectionPolicy,
			GeneratedForPolymorphicReference: field.GeneratedForPolymorphicReference,
		}
		if profile == ProjectionRecordViewV1 {
			// Reference-display enrichment is part of the record-view semantic
			// closure. Preserve the Catalog-declared display field here rather
			// than allowing Records to infer it or consult another metadata path.
			projected.IsNameField = field.IsNameField
		}
		if profileUsesMutationClosure(profile) {
			// Mutation runtime reads the projected capability flags as its source
			// of truth. Apply the generic policy at this projection boundary so
			// the advertised flags cannot be wider than the write guard.
			createable, updateable := fieldcapabilities.ApplyWriteProtectionPolicy(
				field.Createable,
				field.Updateable,
				field.WriteProtectionPolicy,
			)
			projected.Mutation = &runtimeMutationFieldRecord{
				ID: field.ID, DefaultedOnCreate: field.DefaultedOnCreate,
				IsUnique: field.IsUnique, IsNameField: field.IsNameField, Source: field.Source,
				Options: field.Options, WritableReferenceTo: canonicalLooseSet(field.WritableReferenceTo),
				DeleteRule: clonePointer(field.DeleteRule), IsSystem: field.IsSystem,
				Createable: createable, Updateable: updateable,
				DefaultValue: clonePointer(field.DefaultValue), TrackHistory: field.TrackHistory,
				MinValue: clonePointer(field.MinValue), MaxValue: clonePointer(field.MaxValue),
				MinLength: clonePointer(field.MinLength), MaxLength: clonePointer(field.MaxLength),
				Regex: clonePointer(field.Regex), RegexMessage: clonePointer(field.RegexMessage),
				Validator: clonePointer(field.Validator), ValidatorConfig: clonePointer(field.ValidatorConfig),
				ControllingField:      clonePointer(field.ControllingField),
				WriteProtectionPolicy: field.WriteProtectionPolicy,
				SiblingMaxParentField: clonePointer(field.SiblingMaxParentField),
			}
			projected.Mutation.PicklistDependency, _ = cloneJSON(field.PicklistDependency)
			projected.Mutation.RollupConfig, _ = cloneJSON(field.RollupConfig)
		}
		record.Fields = append(record.Fields, projected)
	}
	sort.Slice(record.Fields, func(i, j int) bool {
		return identityLess(record.Fields[i].APIName, record.Fields[j].APIName)
	})
	return record, nil
}

func resolveAssignmentPolicyEligibilityStorage(
	source *models.ObjectMetadata,
	policy *models.ObjectAccessPolicy,
	objects map[string]*models.ObjectMetadata,
) error {
	if policy == nil || policy.Lifecycle == nil ||
		policy.Lifecycle.AssignmentPolicyEligibility == nil {
		return nil
	}
	config := policy.Lifecycle.AssignmentPolicyEligibility
	config.Role = models.CanonicalAssignmentPolicyEligibilitySourceRole(config.Role)
	if objects == nil {
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference",
			"assignment-policy reference storage requires the complete Catalog object graph",
		)
	}
	if source == nil {
		return conflict(ConflictInvalidProjection, "accessPolicy.lifecycle.assignmentPolicyEligibility", "assignment-policy source object is absent")
	}
	physicalName := func(field *models.FieldMetadata) string {
		if name := strings.TrimSpace(field.PhysicalName); name != "" {
			return name
		}
		return strings.TrimSpace(field.APIName)
	}
	requirePhysicalField := func(object *models.ObjectMetadata, fieldName, path string) (*models.FieldMetadata, error) {
		field := models.FindField(object, fieldName)
		if field == nil || !formulastorage.FieldRequiresPhysicalColumn(field) {
			return nil, conflict(
				ConflictInvalidProjection,
				path,
				"assignment-policy source %s requires physical field %s",
				object.APIName,
				fieldName,
			)
		}
		return field, nil
	}
	table, err := source.ResolveReadSourceTable()
	if err != nil {
		return conflict(
			ConflictInvalidReadSource,
			"accessPolicy.lifecycle.assignmentPolicyEligibility",
			"resolve assignment-policy source object %q: %v",
			source.APIName,
			err,
		)
	}
	idField, err := requirePhysicalField(source, constants.FieldID, "accessPolicy.lifecycle.assignmentPolicyEligibility")
	if err != nil {
		return err
	}
	config.ResolvedSourceTable = table
	config.ResolvedSourceIDColumn = physicalName(idField)
	for i := range config.EnabledWhen {
		field, fieldErr := requirePhysicalField(
			source,
			config.EnabledWhen[i].Field,
			fmt.Sprintf("accessPolicy.lifecycle.assignmentPolicyEligibility.enabledWhen.%d", i),
		)
		if fieldErr != nil {
			return fieldErr
		}
		config.EnabledWhen[i].ResolvedColumn = physicalName(field)
	}

	switch config.Role {
	case models.AssignmentPolicyEligibilitySelectorAuthority:
		if config.Selector.Reference != nil || strings.TrimSpace(config.Selector.Field) == "" || strings.TrimSpace(config.SubjectIDField) != "" {
			return conflict(
				ConflictInvalidProjection,
				"accessPolicy.lifecycle.assignmentPolicyEligibility",
				"selector_authority %s requires one direct selector and no subjectIdField",
				source.APIName,
			)
		}
		selectorField, fieldErr := requirePhysicalField(
			source,
			config.Selector.Field,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.field",
		)
		if fieldErr != nil {
			return fieldErr
		}
		config.Selector.ResolvedFieldColumn = physicalName(selectorField)
		return nil
	case models.AssignmentPolicyEligibilitySubjectPopulation:
		if config.Selector.Reference == nil || strings.TrimSpace(config.Selector.Field) != "" || strings.TrimSpace(config.SubjectIDField) == "" {
			return conflict(
				ConflictInvalidProjection,
				"accessPolicy.lifecycle.assignmentPolicyEligibility",
				"subject_population %s requires one reference selector and subjectIdField",
				source.APIName,
			)
		}
	default:
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.role",
			"assignment-policy source %s has unsupported role %q",
			source.APIName,
			config.Role,
		)
	}
	subjectField, err := requirePhysicalField(
		source,
		config.SubjectIDField,
		"accessPolicy.lifecycle.assignmentPolicyEligibility.subjectIdField",
	)
	if err != nil {
		return err
	}
	config.ResolvedSubjectIDColumn = physicalName(subjectField)
	reference := config.Selector.Reference
	sourceReferenceField, err := requirePhysicalField(
		source,
		reference.SourceField,
		"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference.sourceField",
	)
	if err != nil {
		return err
	}
	reference.ResolvedSourceFieldColumn = physicalName(sourceReferenceField)
	target := objects[foldIdentity(reference.TargetObject)]
	if target == nil {
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference.targetObject",
			"assignment-policy target object %q is absent",
			reference.TargetObject,
		)
	}
	targetEligibility := (*models.AssignmentPolicyEligibilityLifecyclePolicy)(nil)
	if target.AccessPolicy != nil && target.AccessPolicy.Lifecycle != nil {
		targetEligibility = target.AccessPolicy.Lifecycle.AssignmentPolicyEligibility
	}
	if targetEligibility == nil ||
		models.CanonicalAssignmentPolicyEligibilitySourceRole(targetEligibility.Role) != models.AssignmentPolicyEligibilitySelectorAuthority ||
		!strings.EqualFold(strings.TrimSpace(targetEligibility.Selector.Field), strings.TrimSpace(reference.TargetField)) {
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference.targetObject",
			"assignment-policy target %s must declare selector_authority on field %s",
			reference.TargetObject,
			reference.TargetField,
		)
	}
	targetTable, err := target.ResolveReadSourceTable()
	if err != nil {
		return conflict(
			ConflictInvalidReadSource,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference.targetObject",
			"resolve assignment-policy target object %q: %v",
			reference.TargetObject,
			err,
		)
	}
	targetIDField := models.FindField(target, "Id")
	selectorField := models.FindField(target, reference.TargetField)
	if targetIDField == nil || selectorField == nil {
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference",
			"assignment-policy target %s requires Id and %s fields",
			reference.TargetObject,
			reference.TargetField,
		)
	}
	if !formulastorage.FieldRequiresPhysicalColumn(targetIDField) ||
		!formulastorage.FieldRequiresPhysicalColumn(selectorField) {
		return conflict(
			ConflictInvalidProjection,
			"accessPolicy.lifecycle.assignmentPolicyEligibility.selector.reference.targetField",
			"assignment-policy target %s requires physical Id and %s columns",
			reference.TargetObject,
			reference.TargetField,
		)
	}
	reference.ResolvedTargetTable = targetTable
	reference.ResolvedTargetIDColumn = physicalName(targetIDField)
	reference.ResolvedTargetFieldColumn = physicalName(selectorField)
	return nil
}

// buildEffectivePermissionRuntimeObject projects only the metadata facts read
// by PermissionService while it computes an effective object/FLS response.
// Keeping this separate from record read/mutation profiles prevents permission
// inspection from inheriting physical storage or executable behavior.
func buildEffectivePermissionRuntimeObject(source *models.ObjectMetadata) (runtimeObjectRecord, error) {
	if source == nil {
		return runtimeObjectRecord{}, conflict(ConflictInvalidProjection, "objects", "effective-permission object is nil")
	}
	accessPolicy, err := projectAccessPolicy(source.AccessPolicy, ProjectionEffectivePermissionsV1, RuntimeMetadataViewSchemaV21)
	if err != nil {
		return runtimeObjectRecord{}, err
	}
	record := runtimeObjectRecord{
		APIName:                 source.APIName,
		RequiredReadPermission:  source.RequiredReadPermission,
		RequiredWritePermission: source.RequiredWritePermission,
		TableType:               source.TableType,
		AccessPolicy:            accessPolicy,
		SharingModel:            source.SharingModel,
		Fields:                  make([]runtimeFieldRecord, 0, len(source.Fields)),
	}
	for i := range source.Fields {
		field := source.Fields[i]
		record.Fields = append(record.Fields, runtimeFieldRecord{
			APIName:              field.APIName,
			Type:                 field.Type,
			Required:             field.Required,
			IsMasterDetail:       field.IsMasterDetail,
			ReadProtectionPolicy: field.ReadProtectionPolicy,
		})
	}
	sort.Slice(record.Fields, func(i, j int) bool {
		return identityLess(record.Fields[i].APIName, record.Fields[j].APIName)
	})
	return record, nil
}

func projectAccessPolicy(source *models.ObjectAccessPolicy, profile ProjectionProfile, runtimeSchema string) (*models.ObjectAccessPolicy, error) {
	if source == nil {
		return nil, nil
	}
	result := &models.ObjectAccessPolicy{Mode: source.Mode}
	var err error
	if profile != ProjectionEffectivePermissionsV1 {
		rowRead, err := cloneJSON(source.RowRead)
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy.rowRead", "clone row-read policy: %v", err)
		}
		result.RowRead = rowRead
	}

	available := map[string]*models.AccessPolicyExpr{
		"objectRead": source.ObjectRead, "objectCreate": source.ObjectCreate,
		"objectEdit": source.ObjectEdit, "objectDelete": source.ObjectDelete,
		"fieldRead": source.FieldRead, "fieldEdit": source.FieldEdit,
	}
	var roots []string
	switch profile {
	case ProjectionSelectedRootReadV1, ProjectionRecordQueryV1, ProjectionReportExecutionV1, ProjectionApprovalReadV1:
		roots = []string{"objectRead", "fieldRead"}
	case ProjectionRecordAccessV1:
		roots = []string{"objectRead", "objectEdit", "objectDelete"}
	case ProjectionRecordViewV1:
		if recordViewHasAccessDecisionClosure(profile, runtimeSchema) {
			roots = []string{"objectRead", "objectEdit", "objectDelete", "fieldRead"}
		} else {
			roots = []string{"objectRead", "fieldRead"}
		}
	case ProjectionRecordMutationV1, ProjectionApprovalExecutionV1:
		roots = []string{"objectRead", "objectCreate", "objectEdit", "objectDelete", "fieldRead", "fieldEdit"}
	case ProjectionEffectivePermissionsV1:
		roots = []string{"objectRead", "objectCreate", "objectEdit", "objectDelete", "fieldRead", "fieldEdit"}
	default:
		return nil, conflict(ConflictInvalidProjection, "profile", "unsupported projection profile %q", profile)
	}
	needed := make(map[string]bool, len(roots))
	queue := append([]string(nil), roots...)
	for _, root := range roots {
		needed[root] = true
	}
	for len(queue) > 0 {
		operation := queue[0]
		queue = queue[1:]
		expr := available[operation]
		for _, inherit := range inheritedOperations(expr) {
			target := normalizePolicyOperation(inherit)
			if target == "" || available[target] == nil {
				return nil, conflict(ConflictInvalidProjection, "accessPolicy."+operation, "unknown inherited operation %q", inherit)
			}
			if !needed[target] {
				needed[target] = true
				queue = append(queue, target)
			}
		}
	}
	for operation := range needed {
		expr, err := cloneJSON(available[operation])
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy."+operation, "clone access expression: %v", err)
		}
		switch operation {
		case "objectRead":
			result.ObjectRead = expr
		case "objectCreate":
			result.ObjectCreate = expr
		case "objectEdit":
			result.ObjectEdit = expr
		case "objectDelete":
			result.ObjectDelete = expr
		case "fieldRead":
			result.FieldRead = expr
		case "fieldEdit":
			result.FieldEdit = expr
		}
	}
	if profile == ProjectionRecordAccessV1 || recordViewHasAccessDecisionClosure(profile, runtimeSchema) || profile == ProjectionEffectivePermissionsV1 {
		blocker, err := projectGenericDeleteBlocker(source.WriteValidation)
		if err != nil {
			return nil, err
		}
		result.WriteValidation = blocker
	}
	if profileUsesMutationClosure(profile) {
		result.RecordMutation, err = cloneJSON(source.RecordMutation)
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy.recordMutation", "clone record-mutation policy: %v", err)
		}
		result.Lifecycle, err = cloneJSON(source.Lifecycle)
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy.lifecycle", "clone lifecycle policy: %v", err)
		}
		result.CacheInvalidation, err = cloneJSON(source.CacheInvalidation)
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy.cacheInvalidation", "clone cache-invalidation policy: %v", err)
		}
		result.WriteValidation, err = cloneJSON(source.WriteValidation)
		if err != nil {
			return nil, conflict(ConflictEncoding, "accessPolicy.writeValidation", "clone write-validation policy: %v", err)
		}
	}
	return result, nil
}

func recordViewHasAccessDecisionClosure(profile ProjectionProfile, runtimeSchema string) bool {
	return profile == ProjectionRecordViewV1 && runtimeSchema == RuntimeMetadataViewSchemaV24
}

func projectGenericDeleteBlocker(source *models.RecordWriteValidationAccessPolicy) (*models.RecordWriteValidationAccessPolicy, error) {
	if source == nil {
		return nil, nil
	}
	var match *models.RecordWriteValidationPolicy
	for i := range source.Validators {
		validator := source.Validators[i]
		if !strings.EqualFold(strings.TrimSpace(validator.Phase), "before_delete") ||
			!strings.EqualFold(strings.TrimSpace(validator.Capability), constants.RecordWriteValidationUserDeleteLifecycle) {
			continue
		}
		if match != nil {
			return nil, conflict(ConflictInvalidAccessPolicy, "accessPolicy.writeValidation.validators", "generic delete blocker is declared more than once")
		}
		copyValidator := validator
		match = &copyValidator
	}
	if match == nil {
		return nil, nil
	}
	return &models.RecordWriteValidationAccessPolicy{Validators: []models.RecordWriteValidationPolicy{*match}}, nil
}

func inheritedOperations(expr *models.AccessPolicyExpr) []string {
	if expr == nil {
		return nil
	}
	result := []string{}
	var walk func(*models.AccessPolicyExpr)
	walk = func(current *models.AccessPolicyExpr) {
		if current == nil {
			return
		}
		if current.Inherit != "" {
			result = append(result, strings.TrimSpace(current.Inherit))
		}
		for i := range current.AllOf {
			walk(&current.AllOf[i])
		}
		for i := range current.AnyOf {
			walk(&current.AnyOf[i])
		}
		walk(current.Not)
	}
	walk(expr)
	return result
}

func validateRuntimePolicyDependencies(objects []runtimeObjectRecord, included map[string]struct{}) error {
	for _, object := range objects {
		if object.AccessPolicy == nil {
			continue
		}
		if accessPolicyHasExpressions(object.AccessPolicy) && !strings.EqualFold(strings.TrimSpace(object.AccessPolicy.Mode), "derived") {
			return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.mode", "access expressions require derived ownership mode")
		}
		if err := validateAccessPolicyInheritGraph(object); err != nil {
			return err
		}
		fields := make(map[string]struct{}, len(object.Fields))
		for _, field := range object.Fields {
			fields[foldIdentity(field.APIName)] = struct{}{}
		}
		for _, expr := range []*models.AccessPolicyExpr{
			object.AccessPolicy.ObjectRead, object.AccessPolicy.ObjectCreate,
			object.AccessPolicy.ObjectEdit, object.AccessPolicy.ObjectDelete,
			object.AccessPolicy.FieldRead, object.AccessPolicy.FieldEdit,
		} {
			var walk func(*models.AccessPolicyExpr) error
			walk = func(current *models.AccessPolicyExpr) error {
				if current == nil {
					return nil
				}
				forms := 0
				for _, present := range []bool{
					len(current.AllOf) > 0, len(current.AnyOf) > 0, current.Not != nil,
					strings.TrimSpace(current.Inherit) != "", strings.TrimSpace(current.Kind) != "",
				} {
					if present {
						forms++
					}
				}
				if forms != 1 {
					return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy", "access expression must declare exactly one composition or predicate form")
				}
				if target := strings.TrimSpace(current.Object); target != "" {
					if _, exists := included[foldIdentity(target)]; !exists {
						return conflict(ConflictInvalidProjection, "objects."+object.APIName+".accessPolicy", "policy references object %q outside the projection", target)
					}
				}
				switch strings.ToLower(strings.TrimSpace(current.Kind)) {
				case "":
				case "objectpermission":
					if strings.TrimSpace(current.Object) == "" || !validObjectPermissionOperation(current.Operation) {
						return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy", "objectPermission requires an in-view object and a supported operation")
					}
				case "systempermission":
					if strings.TrimSpace(current.APIName) == "" {
						return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy", "systemPermission requires apiName")
					}
				case "recordfieldequalscurrentuser":
					if _, exists := fields[foldIdentity(strings.TrimSpace(current.Field))]; !exists {
						return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy", "record policy references absent field %q", current.Field)
					}
				default:
					return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy", "unsupported access expression kind %q", current.Kind)
				}
				for i := range current.AllOf {
					if err := walk(&current.AllOf[i]); err != nil {
						return err
					}
				}
				for i := range current.AnyOf {
					if err := walk(&current.AnyOf[i]); err != nil {
						return err
					}
				}
				return walk(current.Not)
			}
			if err := walk(expr); err != nil {
				return err
			}
		}
		if err := validateRowReadPolicy(object, fields); err != nil {
			return err
		}
	}
	return nil
}

func accessPolicyHasExpressions(policy *models.ObjectAccessPolicy) bool {
	return policy != nil && (policy.ObjectRead != nil || policy.ObjectCreate != nil || policy.ObjectEdit != nil ||
		policy.ObjectDelete != nil || policy.FieldRead != nil || policy.FieldEdit != nil)
}

func validateAccessPolicyInheritGraph(object runtimeObjectRecord) error {
	policy := object.AccessPolicy
	available := map[string]*models.AccessPolicyExpr{
		"objectRead": policy.ObjectRead, "objectCreate": policy.ObjectCreate,
		"objectEdit": policy.ObjectEdit, "objectDelete": policy.ObjectDelete,
		"fieldRead": policy.FieldRead, "fieldEdit": policy.FieldEdit,
	}
	var visit func(string, map[string]bool) error
	visit = func(operation string, stack map[string]bool) error {
		if stack[operation] {
			return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy."+operation, "access policy inherit cycle")
		}
		stack[operation] = true
		defer delete(stack, operation)
		for _, inherited := range inheritedOperations(available[operation]) {
			target := normalizePolicyOperation(inherited)
			if target == "" || available[target] == nil {
				return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy."+operation, "inherited operation %q is absent or unsupported", inherited)
			}
			if err := visit(target, stack); err != nil {
				return err
			}
		}
		return nil
	}
	for operation, expr := range available {
		if expr != nil {
			if err := visit(operation, map[string]bool{}); err != nil {
				return err
			}
		}
	}
	return nil
}

func normalizePolicyOperation(operation string) string {
	switch strings.ToLower(strings.TrimSpace(operation)) {
	case "objectread", "read":
		return "objectRead"
	case "objectcreate", "create":
		return "objectCreate"
	case "objectedit", "edit":
		return "objectEdit"
	case "objectdelete", "delete":
		return "objectDelete"
	case "fieldread":
		return "fieldRead"
	case "fieldedit":
		return "fieldEdit"
	default:
		return ""
	}
}

func validObjectPermissionOperation(operation string) bool {
	switch strings.ToLower(strings.TrimSpace(operation)) {
	case "read", "create", "edit", "delete", "viewallrecords", "modifyallrecords":
		return true
	default:
		return false
	}
}

func validateRowReadPolicy(object runtimeObjectRecord, fields map[string]struct{}) error {
	policy := object.AccessPolicy.RowRead
	if policy == nil {
		return nil
	}
	requireField := func(name string) error {
		if _, exists := fields[foldIdentity(strings.TrimSpace(name))]; !exists {
			return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.rowRead", "row-read policy references absent field %q", name)
		}
		return nil
	}
	switch strings.ToLower(strings.TrimSpace(policy.Kind)) {
	case "targetrecordread":
		if len(policy.TargetAssociations) > 0 {
			if strings.TrimSpace(policy.TargetObjectField) != "" || strings.TrimSpace(policy.TargetRecordField) != "" {
				return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.rowRead", "target-record policy cannot mix targetAssociations with legacy target fields")
			}
			seen := make(map[string]struct{}, len(policy.TargetAssociations))
			for _, association := range policy.TargetAssociations {
				if err := requireField(association.TargetObjectField); err != nil {
					return err
				}
				if err := requireField(association.TargetRecordField); err != nil {
					return err
				}
				key := foldIdentity(association.TargetObjectField) + "\x00" + foldIdentity(association.TargetRecordField)
				if _, duplicate := seen[key]; duplicate {
					return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.rowRead", "target-record policy declares duplicate target association")
				}
				seen[key] = struct{}{}
			}
			return nil
		}
		associations, valid := policy.TargetRecordReadAssociations()
		if !valid || len(associations) != 1 {
			return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.rowRead", "target-record policy requires at least one target association")
		}
		if err := requireField(associations[0].TargetObjectField); err != nil {
			return err
		}
		return requireField(associations[0].TargetRecordField)
	case "ownervisibility":
		if strings.TrimSpace(policy.OwnerField) != "" {
			if err := requireField(policy.OwnerField); err != nil {
				return err
			}
		}
		return requireField(policy.VisibilityField)
	default:
		return conflict(ConflictInvalidAccessPolicy, "objects."+object.APIName+".accessPolicy.rowRead", "unsupported row-read policy kind %q", policy.Kind)
	}
}

// targetRecordRead evaluates a dynamic target object stored in each record.
// The current PermissionService first scans GetSchemas to determine every
// target the caller may read, then recursively evaluates the selected target's
// RLS. Catalog cannot narrow that set without user/security state, so the least
// privileged complete projection is the full object schema set for the same
// Catalog revision. Keep this explicit: a projection author must request the
// complete closure; the compiler never silently widens scope.
func validateProjectionProfile(path string, profile ProjectionProfile) error {
	switch profile {
	case ProjectionSelectedRootReadV1, ProjectionRecordAccessV1, ProjectionRecordQueryV1, ProjectionRecordMutationV1, ProjectionRecordViewV1, ProjectionReportExecutionV1, ProjectionCommandAcceptanceV1, ProjectionApprovalExecutionV1, ProjectionApprovalReadV1, ProjectionEffectivePermissionsV1, ProjectionPageLayoutResolutionV1, ProjectionLayoutAuthoringV1:
		return nil
	default:
		return conflict(ConflictInvalidProjection, path, "projection profile %q is missing or unsupported", profile)
	}
}

func validateProjectionObjectClosure(path string, profile ProjectionProfile, objectNames []string, objects map[string]*models.ObjectMetadata) error {
	if profile == ProjectionCommandAcceptanceV1 {
		if len(objectNames) != 0 {
			return conflict(ConflictInvalidProjection, path, "command acceptance must not receive object metadata")
		}
		return nil
	}
	if profile == ProjectionEffectivePermissionsV1 {
		return nil
	}
	if profile == ProjectionPageLayoutResolutionV1 || profile == ProjectionLayoutAuthoringV1 {
		return nil
	}
	if profile == ProjectionRecordAccessV1 {
		return validateFullObjectClosure(path, "record-access object-type resolution", objectNames, objects)
	}
	if profileUsesMutationClosure(profile) {
		return validateFullObjectClosure(path, "record-mutation engine and policy resolution", objectNames, objects)
	}
	if profile == ProjectionRecordQueryV1 {
		return validateFullObjectClosure(path, "record-query relationship and policy resolution", objectNames, objects)
	}
	if profile == ProjectionRecordViewV1 {
		return validateFullObjectClosure(path, "record-view reference display and policy resolution", objectNames, objects)
	}
	if profile == ProjectionReportExecutionV1 {
		return validateFullObjectClosure(path, "report execution and policy resolution", objectNames, objects)
	}
	return validateTargetRecordReadClosure(path, objectNames, objects)
}

func validateFullObjectClosure(path, reason string, objectNames []string, objects map[string]*models.ObjectMetadata) error {
	included := make(map[string]struct{}, len(objectNames))
	for _, name := range objectNames {
		included[foldIdentity(name)] = struct{}{}
	}
	missing := make([]string, 0, len(objects)-len(included))
	for key, object := range objects {
		if _, exists := included[key]; !exists {
			missing = append(missing, object.APIName)
		}
	}
	if len(missing) == 0 {
		return nil
	}
	sort.Slice(missing, func(i, j int) bool { return identityLess(missing[i], missing[j]) })
	return conflict(ConflictInvalidProjection, path, "%s requires the full same-revision schema closure; missing objects: %s", reason, strings.Join(missing, ", "))
}

func validateTargetRecordReadClosure(path string, objectNames []string, objects map[string]*models.ObjectMetadata) error {
	included := make(map[string]struct{}, len(objectNames))
	requiresFullClosure := false
	for _, name := range objectNames {
		key := foldIdentity(name)
		included[key] = struct{}{}
		object := objects[key]
		if object == nil {
			continue
		}
		policy := object.RowReadPolicy()
		if policy != nil && strings.EqualFold(strings.TrimSpace(policy.Kind), "targetRecordRead") {
			requiresFullClosure = true
		}
	}
	if !requiresFullClosure {
		return nil
	}
	return validateFullObjectClosure(path, "targetRecordRead", objectNames, objects)
}

func validateLineage(lineage Lineage) error {
	if lineage.CatalogRevision == 0 {
		return conflict(ConflictMissingRevision, "lineage.catalogRevision", "catalog revision must be explicit and non-zero")
	}
	if lineage.SchemaGeneration == 0 {
		return conflict(ConflictMissingRevision, "lineage.schemaGeneration", "schema generation must be explicit and non-zero")
	}
	if lineage.EngineGeneration == 0 {
		return conflict(ConflictMissingRevision, "lineage.engineGeneration", "engine generation must be explicit and non-zero")
	}
	return nil
}

func requireSourceRevision(path string, expected, actual CatalogRevision) error {
	if actual == 0 {
		return conflict(ConflictMissingRevision, path+".catalogRevision", "source revision is missing")
	}
	if actual != expected {
		return conflict(ConflictMixedRevision, path+".catalogRevision", "source revision %d does not match snapshot revision %d", actual, expected)
	}
	return nil
}

func requireCanonicalIdentity(path, value string) error {
	if value == "" || strings.TrimSpace(value) != value {
		return conflict(ConflictInvalidIdentity, path, "identity must be non-blank and have no surrounding whitespace")
	}
	return nil
}

func requireCatalogObjectAPIName(path, value string) error {
	if _, err := catalogassignmentidentityv1.NormalizeObjectAPIName(value); err != nil {
		return conflict(ConflictInvalidIdentity, path, "Catalog object-key v1: %v", err)
	}
	return nil
}

func canonicalOptionalIdentity(path string, value *string) (*string, error) {
	if value == nil || *value == "" {
		return nil, nil
	}
	if err := requireCanonicalIdentity(path, *value); err != nil {
		return nil, err
	}
	result := *value
	return &result, nil
}

func canonicalIdentitySet(path string, values []string) ([]string, error) {
	result := make([]string, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for i, value := range values {
		if err := requireCanonicalIdentity(fmt.Sprintf("%s[%d]", path, i), value); err != nil {
			return nil, err
		}
		key := foldIdentity(value)
		if _, exists := seen[key]; exists {
			return nil, conflict(ConflictInvalidProjection, path, "identity %q is duplicated case-insensitively", value)
		}
		seen[key] = struct{}{}
		result = append(result, value)
	}
	sort.Slice(result, func(i, j int) bool { return identityLess(result[i], result[j]) })
	return result, nil
}

func canonicalLooseSet(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	result := append([]string(nil), values...)
	sort.Slice(result, func(i, j int) bool { return identityLess(result[i], result[j]) })
	write := 0
	for _, value := range result {
		if write > 0 && foldIdentity(result[write-1]) == foldIdentity(value) {
			continue
		}
		result[write] = value
		write++
	}
	return result[:write]
}

func foldIdentity(value string) string { return strings.ToLower(value) }

func identityLess(left, right string) bool {
	leftFolded, rightFolded := foldIdentity(left), foldIdentity(right)
	if leftFolded == rightFolded {
		return left < right
	}
	return leftFolded < rightFolded
}

func relationshipKey(record relationshipRecord) string {
	return strings.Join([]string{
		foldIdentity(record.ChildObjectAPIName), foldIdentity(record.ParentObjectAPIName),
		foldIdentity(record.FieldAPIName), foldIdentity(record.RelationshipName),
		foldIdentity(record.RelationshipType), fmt.Sprintf("%t", record.IsSharingParent),
	}, "\x00")
}

func runtimeRelationshipKey(record runtimeRelationshipRecord) string {
	return strings.Join([]string{
		foldIdentity(record.ChildObjectAPIName), foldIdentity(record.ParentObjectAPIName),
		foldIdentity(record.FieldAPIName), foldIdentity(record.RelationshipType),
		fmt.Sprintf("%t", record.IsSharingParent),
	}, "\x00")
}

func cloneJSON[T any](source T) (T, error) {
	var target T
	data, err := json.Marshal(source)
	if err != nil {
		return target, err
	}
	if err := json.Unmarshal(data, &target); err != nil {
		return target, err
	}
	return target, nil
}

func marshalCanonical(value any) ([]byte, error) {
	var buffer bytes.Buffer
	encoder := json.NewEncoder(&buffer)
	encoder.SetEscapeHTML(false)
	if err := encoder.Encode(value); err != nil {
		return nil, err
	}
	return bytes.TrimSuffix(buffer.Bytes(), []byte("\n")), nil
}
