package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"slices"
	"strings"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	navigationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationnavigationv1"
	approvalnotificationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalnotificationflowv1"
	approvalsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/approvalv1"
	auditsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/auditlogv1"
	bulkingestsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/bulkingestv1"
	commandsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commandv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
	filessemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/filesv1"
	flowcommandsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv1"
	flowsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowv1"
	layoutauthoringsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
	layoutauthoringv2semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv2"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	mailboxauthorizationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/mailboxauthorizationv1"
	metadataadminsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/metadataadminreadv1"
	outboundintentsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/outboundintentv1"
	pagelayoutsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/pagelayoutresolutionv1"
	permissionauthoringsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionauthoringv1"
	querysemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	accesssemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordaccessv1"
	attachmentsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordattachmentsv1"
	defaultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recorddefaultsv1"
	mutationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
	viewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordviewv1"
	recyclemutationsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinmutationv1"
	recyclesemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recyclebinv1"
	relatedsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/relatedcountsv1"
	reportsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/reportv1"
	searchsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/searchv1"
	sessionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/sessionv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/componentbehavior"
	"github.com/pingcap-inc/nexuscrm-controlplane/releaseartifact"
	"github.com/pingcap-inc/nexuscrm-controlplane/strictjson"
)

const (
	crmAppID                           = "io.nexuscrm.sfcrm"
	crmPublisherID                     = "io.nexuscrm"
	crmPackName                        = "salesforce-crm-standard"
	crmRecordAccessCapability          = "io.nexuscrm.sfcrm.ui.record-access.v1"
	crmRecordAccessHandlerRef          = "io.nexuscrm.sfcrm.handler.ui.record-access.v1"
	crmQueryCapability                 = "io.nexuscrm.sfcrm.query.v1"
	crmQueryHandlerRef                 = "io.nexuscrm.sfcrm.handler.query.v1"
	crmRelatedCountCapability          = "io.nexuscrm.sfcrm.related-list-counts.v1"
	crmRelatedCountHandlerRef          = "io.nexuscrm.sfcrm.handler.related-list-counts.v1"
	crmRecycleBinCapability            = "io.nexuscrm.sfcrm.recycle-bin-read.v1"
	crmRecycleBinHandlerRef            = "io.nexuscrm.sfcrm.handler.recycle-bin-read.v1"
	crmRecycleBinMutationCapability    = "io.nexuscrm.sfcrm.recycle-bin-mutation.v1"
	crmRecycleBinMutationHandlerRef    = "io.nexuscrm.sfcrm.handler.recycle-bin-mutation.v1"
	crmRecordViewCapability            = "io.nexuscrm.sfcrm.ui.record-view-read.v1"
	crmRecordViewHandlerRef            = "io.nexuscrm.sfcrm.handler.ui.record-view-read.v1"
	crmRecordDefaultsCapability        = "io.nexuscrm.sfcrm.ui.record-defaults.v1"
	crmRecordDefaultsHandlerRef        = "io.nexuscrm.sfcrm.handler.ui.record-defaults.v1"
	crmMutationCapability              = "io.nexuscrm.sfcrm.record-mutation.v1"
	crmMutationHandlerRef              = "io.nexuscrm.sfcrm.handler.record-mutation.v1"
	crmFilesCapability                 = "io.nexuscrm.sfcrm.record-files.v1"
	crmFilesHandlerRef                 = "io.nexuscrm.sfcrm.handler.record-files.v1"
	crmBulkIngestCapability            = "io.nexuscrm.sfcrm.bulk-ingest.v1"
	crmBulkIngestHandlerRef            = "io.nexuscrm.sfcrm.handler.bulk-ingest.v1"
	crmEffectivePermissionsCapability  = "io.nexuscrm.sfcrm.effective-permissions.v1"
	crmEffectivePermissionsHandlerRef  = "io.nexuscrm.sfcrm.handler.effective-permissions.v1"
	crmSearchScopeOrderCapability      = "io.nexuscrm.sfcrm.search-scope-order.v1"
	crmSearchScopeOrderHandlerRef      = "io.nexuscrm.sfcrm.handler.search-scope-order.v1"
	crmSearchCapability                = "io.nexuscrm.sfcrm.search.v1"
	crmSearchHandlerRef                = "io.nexuscrm.sfcrm.handler.search.v1"
	crmFlowActionsCapability           = "io.nexuscrm.sfcrm.flow-actions.v1"
	crmFlowActionsHandlerRef           = "io.nexuscrm.sfcrm.handler.flow-actions.v1"
	crmAnalyticsReadCapability         = "io.nexuscrm.sfcrm.analytics-read.v1"
	crmAnalyticsReadHandlerRef         = "io.nexuscrm.sfcrm.handler.analytics-read.v1"
	crmAuditLogReadCapability          = "io.nexuscrm.sfcrm.audit-log-read.v1"
	crmAuditLogReadHandlerRef          = "io.nexuscrm.sfcrm.handler.audit-log-read.v1"
	crmApprovalLifecycleCapability     = "io.nexuscrm.sfcrm.approval-lifecycle.v1"
	crmApprovalLifecycleHandlerRef     = "io.nexuscrm.sfcrm.handler.approval-lifecycle.v1"
	crmApprovalReadCapability          = "io.nexuscrm.sfcrm.approval-read.v1"
	crmApprovalReadHandlerRef          = "io.nexuscrm.sfcrm.handler.approval-read.v1"
	crmApprovalFlowReadCapability      = "io.nexuscrm.sfcrm.approval-notification-flows.v1"
	crmApprovalFlowReadHandlerRef      = "io.nexuscrm.sfcrm.handler.approval-notification-flows.v1"
	crmApplicationNavigationCapability = "io.nexuscrm.sfcrm.application-navigation.v1"
	crmApplicationNavigationHandlerRef = "io.nexuscrm.sfcrm.handler.application-navigation.v1"
	crmUIAppsCapability                = "io.nexuscrm.sfcrm.ui-apps.v1"
	crmUIAppsHandlerRef                = "io.nexuscrm.sfcrm.handler.ui-apps.v1"
	crmStandardActionsCapability       = "io.nexuscrm.sfcrm.standard-actions.v1"
	crmStandardActionsHandlerRef       = "io.nexuscrm.sfcrm.handler.standard-actions.v1"
	crmPrivateListViewsCapability      = "io.nexuscrm.sfcrm.private-list-views.v1"
	crmPrivateListViewsHandlerRef      = "io.nexuscrm.sfcrm.handler.private-list-views.v1"
	crmSCIMCapability                  = "io.nexuscrm.sfcrm.scim.v1"
	crmSCIMHandlerRef                  = "io.nexuscrm.sfcrm.handler.scim.v1"
	crmMetadataAdminReadCapability     = "io.nexuscrm.sfcrm.metadata-admin-read.v1"
	crmMetadataAdminReadHandlerRef     = "io.nexuscrm.sfcrm.handler.metadata-admin-read.v1"
	crmPermissionAuthoringCapability   = "io.nexuscrm.sfcrm.permission-authoring.v1"
	crmLayoutAuthoringCapability       = "io.nexuscrm.sfcrm.layout-authoring.v1"
	crmLayoutAuthoringHandlerRef       = "io.nexuscrm.sfcrm.handler.layout-authoring.v1"
	crmPermissionAuthoringHandlerRef   = "io.nexuscrm.sfcrm.handler.permission-authoring.v1"
	// The attachment follow-up receives a distinct Catalog receipt and Runtime
	// delegation capability. It intentionally shares the record-view metadata
	// projection, but cannot share the app capability: one handler binding owns
	// one exact runtime compatibility receipt.
	crmRecordViewAttachmentSourceCapability  = "io.nexuscrm.sfcrm.ui.record-view-attachments.v1"
	crmRecordViewAttachmentSourceHandlerRef  = "io.nexuscrm.sfcrm.handler.ui.record-view-attachments.v1"
	crmRecordViewComponent                   = "metadata.record-view.v1"
	crmRuntimeCommandComponent               = "metadata.runtime-command-registration.v1"
	crmRuntimeFlowCommandV1Source            = "app-release/runtime-flow-command.json"
	crmRuntimeFlowCommandV2Source            = "app-release/runtime-flow-command-v2.json"
	crmRuntimeFlowCommandV3Source            = "app-release/runtime-flow-command-v3.json"
	crmMailboxAuthorizationRuntimeCapability = mailboxauthorizationsemantic.CapabilityName
	crmMetadataAdminReadRuntimeCapability    = metadataadminsemantic.CapabilityName
)

type runtimeFlowCommandSource struct {
	path        string
	businessKey string
}

type releaseCapability struct {
	capabilityID                  string
	handlerRef                    string
	runtimeCapability             string
	additionalRuntimeCapabilities []string
}

// crmReleaseCapabilities defines the builder input for released versions and
// the next source candidate. A version becomes immutable only when its
// canonical artifact is committed under releases/io.nexuscrm.sfcrm/<version>;
// a source-only entry does not publish or reserve an AppRelease identity.
var crmReleaseCapabilities = map[string][]releaseCapability{
	"0.2.0": {{
		capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef,
		runtimeCapability: accesssemantic.CapabilityName,
	}},
	"0.3.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
	},
	"0.4.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
	},
	"0.5.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
	},
	"0.6.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
	},
	"0.7.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
	},
	"0.8.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
	},
	"0.9.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
	},
	"0.10.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
	},
	"0.11.0": {
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
		{capabilityID: crmFilesCapability, handlerRef: crmFilesHandlerRef, runtimeCapability: filessemantic.CapabilityName},
	},
	"0.12.0": {
		{capabilityID: crmEffectivePermissionsCapability, handlerRef: crmEffectivePermissionsHandlerRef, runtimeCapability: permissionsemantic.CapabilityName},
		{capabilityID: crmFilesCapability, handlerRef: crmFilesHandlerRef, runtimeCapability: filessemantic.CapabilityName},
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
		{
			capabilityID:      crmSearchScopeOrderCapability,
			handlerRef:        crmSearchScopeOrderHandlerRef,
			runtimeCapability: permissionsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
			},
		},
	},
	"0.13.0": {
		{capabilityID: crmEffectivePermissionsCapability, handlerRef: crmEffectivePermissionsHandlerRef, runtimeCapability: permissionsemantic.CapabilityName},
		{capabilityID: crmFilesCapability, handlerRef: crmFilesHandlerRef, runtimeCapability: filessemantic.CapabilityName},
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
		{
			capabilityID:      crmSearchScopeOrderCapability,
			handlerRef:        crmSearchScopeOrderHandlerRef,
			runtimeCapability: searchsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
			},
		},
	},
	"0.14.0": {
		{capabilityID: crmEffectivePermissionsCapability, handlerRef: crmEffectivePermissionsHandlerRef, runtimeCapability: permissionsemantic.CapabilityName},
		{capabilityID: crmFilesCapability, handlerRef: crmFilesHandlerRef, runtimeCapability: filessemantic.CapabilityName},
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
		{
			capabilityID:      crmSearchCapability,
			handlerRef:        crmSearchHandlerRef,
			runtimeCapability: searchsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataV2CapabilityName,
				querysemantic.CapabilityName,
			},
		},
		{
			capabilityID:      crmSearchScopeOrderCapability,
			handlerRef:        crmSearchScopeOrderHandlerRef,
			runtimeCapability: searchsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
			},
		},
	},
	"0.15.0": {
		{capabilityID: crmEffectivePermissionsCapability, handlerRef: crmEffectivePermissionsHandlerRef, runtimeCapability: permissionsemantic.CapabilityName},
		{capabilityID: crmFilesCapability, handlerRef: crmFilesHandlerRef, runtimeCapability: filessemantic.CapabilityName},
		{capabilityID: crmQueryCapability, handlerRef: crmQueryHandlerRef, runtimeCapability: querysemantic.CapabilityName},
		{capabilityID: crmMutationCapability, handlerRef: crmMutationHandlerRef, runtimeCapability: mutationsemantic.CapabilityName},
		{capabilityID: crmRecycleBinCapability, handlerRef: crmRecycleBinHandlerRef, runtimeCapability: recyclesemantic.CapabilityName},
		{capabilityID: crmRelatedCountCapability, handlerRef: crmRelatedCountHandlerRef, runtimeCapability: relatedsemantic.CapabilityName},
		{capabilityID: crmRecordAccessCapability, handlerRef: crmRecordAccessHandlerRef, runtimeCapability: accesssemantic.CapabilityName},
		{capabilityID: crmRecordViewAttachmentSourceCapability, handlerRef: crmRecordViewAttachmentSourceHandlerRef, runtimeCapability: attachmentsemantic.CapabilityName},
		{capabilityID: crmRecordViewCapability, handlerRef: crmRecordViewHandlerRef, runtimeCapability: viewsemantic.CapabilityName},
		{
			capabilityID:      crmSearchCapability,
			handlerRef:        crmSearchHandlerRef,
			runtimeCapability: searchsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataV2CapabilityName,
				querysemantic.CapabilityName,
			},
		},
		{
			capabilityID:      crmSearchScopeOrderCapability,
			handlerRef:        crmSearchScopeOrderHandlerRef,
			runtimeCapability: searchsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
			},
		},
	},
}

// Releases listed here must admit a complete one-to-one selection from every
// L1 handler to one of its declared neutral Runtime dependencies. Catalog
// publishes all handler bindings atomically, while Runtime admits each neutral
// capability once. Historical immutable releases remain reproducible; new
// releases enter this gate before publication.
var crmCatalogSelectionClosedVersions = map[string]struct{}{
	"0.24.0": {},
	"0.25.0": {},
	"0.26.0": {},
	"0.27.0": {},
	"0.28.0": {},
	"0.29.0": {},
	"0.30.0": {},
	"0.31.0": {},
	"0.32.0": {},
	"0.33.0": {},
	"0.34.0": {},
	"0.35.0": {},
	"0.36.0": {},
	"0.39.0": {},
	"0.40.0": {},
	"0.43.0": {},
	"0.44.0": {},
	"0.45.0": {},
	"0.46.0": {},
	"0.47.0": {},
	"0.48.0": {},
	"0.49.0": {},
	"0.50.0": {},
	"0.51.0": {},
	"0.52.0": {},
	"0.53.0": {},
	"0.54.0": {},
	"0.55.0": {},
	"0.56.0": {},
	"0.57.0": {},
	"0.58.0": {},
	"0.59.0": {},
	"0.60.0": {},
	"0.61.0": {},
	"0.62.0": {},
	"0.63.0": {},
	"0.64.0": {},
	"0.65.0": {},
	"0.66.0": {},
	"0.67.0": {},
	"0.68.0": {},
	"0.69.0": {},
	"0.70.0": {},
	"0.71.0": {},
	"0.72.0": {},
	"0.73.0": {},
	"0.74.0": {},
	"0.75.0": {},
}

// A deployable release must also prove that every Runtime requirement left
// unmatched by the one-per-handler serving selection is an explicitly
// supported consumer-only capability. 0.24.0 predates this stronger gate and
// remains reproducible as immutable history; 0.25.0 is the first release that
// closes the complete production composition.
var crmCatalogDeployableVersions = map[string]struct{}{
	"0.25.0": {},
	"0.26.0": {},
	"0.27.0": {},
	"0.28.0": {},
	"0.29.0": {},
	"0.30.0": {},
	"0.31.0": {},
	"0.32.0": {},
	"0.33.0": {},
	"0.34.0": {},
	"0.35.0": {},
	"0.36.0": {},
	"0.39.0": {},
	"0.40.0": {},
	"0.43.0": {},
	"0.44.0": {},
	"0.45.0": {},
	"0.46.0": {},
	"0.47.0": {},
	"0.48.0": {},
	"0.49.0": {},
	"0.50.0": {},
	"0.51.0": {},
	"0.52.0": {},
	"0.53.0": {},
	"0.54.0": {},
	"0.55.0": {},
	"0.56.0": {},
	"0.57.0": {},
	"0.58.0": {},
	"0.59.0": {},
	"0.60.0": {},
	"0.61.0": {},
	"0.62.0": {},
	"0.63.0": {},
	"0.64.0": {},
	"0.65.0": {},
	"0.66.0": {},
	"0.67.0": {},
	"0.68.0": {},
	"0.69.0": {},
	"0.70.0": {},
	"0.71.0": {},
	"0.72.0": {},
	"0.73.0": {},
	"0.74.0": {},
	"0.75.0": {},
}

func init() {
	// 0.16 and 0.17 change only consumer-owned Runtime requirements. Their
	// sfcrm handler surface remains byte-for-byte the reviewed 0.15
	// declaration.
	crmReleaseCapabilities["0.16.0"] =
		slices.Clone(crmReleaseCapabilities["0.15.0"])
	crmReleaseCapabilities["0.17.0"] =
		slices.Clone(crmReleaseCapabilities["0.16.0"])
	crmReleaseCapabilities["0.18.0"] =
		slices.Clone(crmReleaseCapabilities["0.17.0"])
	crmReleaseCapabilities["0.19.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.18.0"]),
		releaseCapability{
			capabilityID:      crmFlowActionsCapability,
			handlerRef:        crmFlowActionsHandlerRef,
			runtimeCapability: flowsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
				permissionsemantic.CapabilityName,
			},
		},
	)
	crmReleaseCapabilities["0.20.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.19.0"]),
		releaseCapability{
			capabilityID:      crmAnalyticsReadCapability,
			handlerRef:        crmAnalyticsReadHandlerRef,
			runtimeCapability: reportsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataV2CapabilityName,
				permissionsemantic.CapabilityName,
				querysemantic.CapabilityName,
			},
		},
	)
	crmReleaseCapabilities["0.21.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.20.0"]),
		releaseCapability{
			capabilityID:      crmApprovalLifecycleCapability,
			handlerRef:        crmApprovalLifecycleHandlerRef,
			runtimeCapability: approvalsemantic.ExecuteCapabilityName,
			additionalRuntimeCapabilities: []string{
				approvalsemantic.ReadCapabilityName,
			},
		},
	)
	crmReleaseCapabilities["0.22.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.21.0"]),
		releaseCapability{
			capabilityID:      crmUIAppsCapability,
			handlerRef:        crmUIAppsHandlerRef,
			runtimeCapability: permissionsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataV2CapabilityName,
			},
		},
	)
	crmReleaseCapabilities["0.23.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.22.0"]),
		releaseCapability{
			capabilityID:      crmStandardActionsCapability,
			handlerRef:        crmStandardActionsHandlerRef,
			runtimeCapability: flowsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataCapabilityName,
				permissionsemantic.CapabilityName,
			},
		},
	)
	// Standard Actions and manual Flow Actions are two Salesforce route
	// families backed by the same sfcrm process and the same neutral Runtime
	// Flow, AppMetadata and effective-permission closure. Publishing them as
	// separate App handlers in 0.23.0 made an exact Catalog serving selection
	// impossible: Catalog and Runtime admit one active L1 selector per neutral
	// Runtime capability. 0.24.0 restores the coarse L1 handler boundary by
	// retaining Flow Actions as the single action handler capability. Both
	// Salesforce route families remain installed in that handler composition.
	crmReleaseCapabilities["0.24.0"] =
		slices.Clone(crmReleaseCapabilities["0.22.0"])
	crmReleaseCapabilities["0.25.0"] =
		slices.Clone(crmReleaseCapabilities["0.24.0"])
	for index := range crmReleaseCapabilities["0.25.0"] {
		if crmReleaseCapabilities["0.25.0"][index].capabilityID ==
			crmApprovalLifecycleCapability {
			crmReleaseCapabilities["0.25.0"][index].additionalRuntimeCapabilities = nil
		}
	}
	crmReleaseCapabilities["0.25.0"] = append(
		crmReleaseCapabilities["0.25.0"],
		releaseCapability{
			capabilityID:      crmApprovalReadCapability,
			handlerRef:        crmApprovalReadHandlerRef,
			runtimeCapability: approvalsemantic.ReadCapabilityName,
		},
	)
	crmReleaseCapabilities["0.26.0"] =
		slices.Clone(crmReleaseCapabilities["0.25.0"])
	crmReleaseCapabilities["0.27.0"] =
		slices.Clone(crmReleaseCapabilities["0.26.0"])
	crmReleaseCapabilities["0.28.0"] =
		slices.Clone(crmReleaseCapabilities["0.27.0"])
	// 0.28.0 is the first AppRelease whose sfcrm handlers consume the complete
	// AppMetadataView V3 projection. Historical AppReleases remain byte-for-byte
	// reproducible and retain their original V1/V2 requirements.
	for index := range crmReleaseCapabilities["0.28.0"] {
		switch crmReleaseCapabilities["0.28.0"][index].capabilityID {
		case crmSearchCapability, crmFlowActionsCapability,
			crmAnalyticsReadCapability, crmUIAppsCapability:
			requirements := slices.Clone(
				crmReleaseCapabilities["0.28.0"][index].additionalRuntimeCapabilities,
			)
			for requirementIndex := range requirements {
				if requirements[requirementIndex] == catalogsemantic.AppMetadataCapabilityName ||
					requirements[requirementIndex] == catalogsemantic.AppMetadataV2CapabilityName {
					requirements[requirementIndex] = catalogsemantic.AppMetadataV3CapabilityName
				}
			}
			crmReleaseCapabilities["0.28.0"][index].additionalRuntimeCapabilities = requirements
		case crmEffectivePermissionsCapability:
			// The coarse metadata/permission projection handler also owns global
			// describe, object describe/layouts, and quick actions. Those route
			// families consume the immutable V3 view; they are not separate L1
			// handlers or separate Runtime services.
			crmReleaseCapabilities["0.28.0"][index].additionalRuntimeCapabilities = []string{
				catalogsemantic.AppMetadataV3CapabilityName,
			}
		case crmRecordViewCapability:
			// record-ui is the composed form of the existing record-view handler.
			// Declare its complete neutral closure instead of inventing one
			// capability per Salesforce route.
			crmReleaseCapabilities["0.28.0"][index].additionalRuntimeCapabilities = []string{
				catalogsemantic.AppMetadataV3CapabilityName,
				permissionsemantic.CapabilityName,
				accesssemantic.CapabilityName,
			}
		}
	}
	// 0.29.0 adds administrator-provisioned Connected App metadata only. It
	// does not add or remove an sfcrm handler or Runtime dependency, so its
	// reviewed capability closure remains exactly the 0.28.0 closure.
	crmReleaseCapabilities["0.29.0"] =
		slices.Clone(crmReleaseCapabilities["0.28.0"])
	// 0.30.0 adds the metadata-owned Task related-record row-read policy. It
	// does not add or remove an sfcrm handler or Runtime dependency, so its
	// reviewed capability closure remains exactly the 0.29.0 closure.
	crmReleaseCapabilities["0.30.0"] =
		slices.Clone(crmReleaseCapabilities["0.29.0"])
	// 0.31.0 extends the metadata-owned activity row-read policy to both
	// supported polymorphic relationship slots. It does not add or remove an
	// sfcrm handler or Runtime dependency, so its reviewed capability closure
	// remains exactly the 0.30.0 closure.
	crmReleaseCapabilities["0.31.0"] =
		slices.Clone(crmReleaseCapabilities["0.30.0"])
	// 0.32.0 adds the existing Salesforce private saved-view route family as
	// one coarse L1 handler backed by the neutral private ListView capability.
	// Standard Salesforce views remain an L1 projection and are not published
	// as L0 metadata.
	crmReleaseCapabilities["0.32.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.31.0"]),
		releaseCapability{
			capabilityID:      crmPrivateListViewsCapability,
			handlerRef:        crmPrivateListViewsHandlerRef,
			runtimeCapability: listviewsemantic.CapabilityName,
		},
	)
	// 0.33.0 replaces the last monolith-owned Lead conversion action with the
	// existing app-blind prepared-update mechanism. The L1 handler surface and
	// neutral Runtime capability closure are unchanged.
	crmReleaseCapabilities["0.33.0"] =
		slices.Clone(crmReleaseCapabilities["0.32.0"])
	// 0.34.0 publishes the CampaignMember launch metadata consumed through the
	// existing V3 Catalog and effective-permission closure. Runtime capabilities are unchanged.
	crmReleaseCapabilities["0.34.0"] =
		slices.Clone(crmReleaseCapabilities["0.33.0"])
	// 0.35.0 restores the Salesforce administrator audit route as a distinct
	// L1 handler over the app-blind Runtime audit reader. The permission name
	// remains L1-owned; Runtime independently enforces the privileged read.
	crmReleaseCapabilities["0.35.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.34.0"]),
		releaseCapability{
			capabilityID: crmAuditLogReadCapability, handlerRef: crmAuditLogReadHandlerRef,
			runtimeCapability:             auditsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{permissionsemantic.CapabilityName},
		},
	)
	// 0.36.0 publishes the protected prepared-update form of the standard
	// Lead-child relink Flow. It changes metadata behavior without changing
	// the reviewed L1 handler or neutral Runtime capability closure.
	crmReleaseCapabilities["0.36.0"] =
		slices.Clone(crmReleaseCapabilities["0.35.0"])
	// 0.39.0 publishes Opportunity.LastStageChangeDate through the L1-owned
	// Flow without changing the reviewed handler or Runtime capability closure.
	crmReleaseCapabilities["0.39.0"] =
		slices.Clone(crmReleaseCapabilities["0.36.0"])
	// 0.40.0 closes the consumer-only Integration requirements for connected
	// mailbox authorization and outbound intent delivery. The public sfcrm
	// handler surface is unchanged.
	crmReleaseCapabilities["0.40.0"] =
		slices.Clone(crmReleaseCapabilities["0.39.0"])
	// 0.43.0 moves the SCIM 2.0 receiving surface into the independently
	// deployed L1 AppService. Session owns Connected App authentication while
	// neutral Query and Mutation remain the only record I/O dependencies. The
	// Helix projection excludes ManagerId, and userName/email reconciliation
	// must resolve to one User before any write.
	crmReleaseCapabilities["0.43.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.40.0"]),
		releaseCapability{
			capabilityID: crmSCIMCapability, handlerRef: crmSCIMHandlerRef,
			runtimeCapability: sessionsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				querysemantic.CapabilityName,
				mutationsemantic.CapabilityName,
			},
		},
	)
	// 0.44.0 registers the FlowCommand v3 envelope used by outbound connector
	// push contracts. The L1 handler and neutral Runtime capability closure are
	// unchanged from 0.43.0.
	crmReleaseCapabilities["0.44.0"] =
		slices.Clone(crmReleaseCapabilities["0.43.0"])
	// 0.45.0 keeps the latest metadata and FlowCommand v3 registration while
	// withdrawing the unfinished SCIM handler from the published App surface.
	// Preserve that published release byte-for-byte.
	crmReleaseCapabilities["0.45.0"] =
		slices.Clone(crmReleaseCapabilities["0.40.0"])
	// 0.46.0 closes the existing Object Manager and Page Layout Manager read
	// routes through one coarse L1 handler backed by the neutral Runtime
	// metadata-administration read port. The handler is workload-only: it is
	// admitted by Runtime security bindings but intentionally has no Catalog
	// serving-artifact projection. It preserves the withdrawal of the unfinished
	// SCIM handler by extending the pre-SCIM 0.40.0 set.
	crmReleaseCapabilities["0.46.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.40.0"]),
		releaseCapability{
			capabilityID:      crmMetadataAdminReadCapability,
			handlerRef:        crmMetadataAdminReadHandlerRef,
			runtimeCapability: crmMetadataAdminReadRuntimeCapability,
		},
	)
	// 0.47.0 preserves the 0.46.0 capability and metadata contract while
	// publishing the D3 AppService consumer that projects effective object
	// listFields from Catalog format 4. The implementation change introduces no
	// new App or Runtime capability.
	crmReleaseCapabilities["0.47.0"] =
		slices.Clone(crmReleaseCapabilities["0.46.0"])
	// 0.48.0 publishes the Salesforce Approval notification Flow discovery
	// route. Its handler reads the neutral notification-safe Flow projection
	// and evaluates the dialect-owned ModifyMetadata permission through the
	// existing effective-permissions Runtime port.
	crmReleaseCapabilities["0.48.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.47.0"]),
		releaseCapability{
			capabilityID:      crmApprovalFlowReadCapability,
			handlerRef:        crmApprovalFlowReadHandlerRef,
			runtimeCapability: approvalnotificationsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				permissionsemantic.CapabilityName,
			},
		},
	)
	// 0.49.0 removes the stale Lead Convert create-preview declaration. The
	// update-bearing Flow remains executable, while its separate output-preview
	// producer continues to serve Campaign Attribution. No Runtime capability or
	// AppService handler changes in this metadata-only release.
	crmReleaseCapabilities["0.49.0"] =
		slices.Clone(crmReleaseCapabilities["0.48.0"])
	// 0.50.0 moves Salesforce Bulk Ingest HTTP ownership into the independent
	// L1 AppService. Runtime owns the neutral durable job lifecycle and bounded
	// upload stream; the old process-local dialect route is removed.
	crmReleaseCapabilities["0.50.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.49.0"]),
		releaseCapability{
			capabilityID:      crmBulkIngestCapability,
			handlerRef:        crmBulkIngestHandlerRef,
			runtimeCapability: bulkingestsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				mutationsemantic.CapabilityName,
			},
		},
	)
	// 0.51.0 replaces the AppMetadata default-layout shortcut with the neutral
	// caller-effective Runtime resolver. The existing coarse permission and
	// record-view handlers keep owning their Salesforce route families; this is
	// an added neutral dependency, not a second L1 handler or metadata model.
	crmReleaseCapabilities["0.51.0"] =
		slices.Clone(crmReleaseCapabilities["0.50.0"])
	for index := range crmReleaseCapabilities["0.51.0"] {
		switch crmReleaseCapabilities["0.51.0"][index].capabilityID {
		case crmEffectivePermissionsCapability, crmRecordViewCapability:
			requirements := append(
				slices.Clone(crmReleaseCapabilities["0.51.0"][index].additionalRuntimeCapabilities),
				pagelayoutsemantic.CapabilityName,
			)
			slices.Sort(requirements)
			crmReleaseCapabilities["0.51.0"][index].additionalRuntimeCapabilities = requirements
		}
	}
	// 0.52.0 completes the existing create Record Defaults route through the
	// independent L1 AppService while retaining the 0.51.0 PageLayout path.
	// L1 owns the Salesforce response shape; Runtime owns user-authorized
	// dynamic defaults and their provenance.
	crmReleaseCapabilities["0.52.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.51.0"]),
		releaseCapability{
			capabilityID:      crmRecordDefaultsCapability,
			handlerRef:        crmRecordDefaultsHandlerRef,
			runtimeCapability: defaultsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				catalogsemantic.AppMetadataV3CapabilityName,
				pagelayoutsemantic.CapabilityName,
				permissionsemantic.CapabilityName,
			},
		},
	)
	// 0.53.0 changes only L1 pack metadata and the transitional action's
	// null-checkbox policy. The independently served handler and Runtime
	// capability closure remain byte-for-byte the reviewed 0.52.0 declaration.
	crmReleaseCapabilities["0.53.0"] =
		slices.Clone(crmReleaseCapabilities["0.52.0"])
	// 0.54.0 completes Clone Defaults under the existing Record Defaults
	// handler. L1 reuses the caller-authorized Record View source row and the
	// existing create-default/PageLayout/permission assembly; no second clone
	// engine or route owner is introduced.
	crmReleaseCapabilities["0.54.0"] =
		slices.Clone(crmReleaseCapabilities["0.53.0"])
	for index := range crmReleaseCapabilities["0.54.0"] {
		if crmReleaseCapabilities["0.54.0"][index].capabilityID != crmRecordDefaultsCapability {
			continue
		}
		requirements := append(
			slices.Clone(crmReleaseCapabilities["0.54.0"][index].additionalRuntimeCapabilities),
			viewsemantic.CapabilityName,
		)
		slices.Sort(requirements)
		crmReleaseCapabilities["0.54.0"][index].additionalRuntimeCapabilities = requirements
	}
	// 0.55.0 completes the existing Recycle Bin write lifecycle through the
	// independent L1 AppService. Runtime remains the sole transaction owner;
	// L1 owns only the Salesforce restore/purge routes and error projection.
	crmReleaseCapabilities["0.55.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.54.0"]),
		releaseCapability{
			capabilityID:      crmRecycleBinMutationCapability,
			handlerRef:        crmRecycleBinMutationHandlerRef,
			runtimeCapability: recyclemutationsemantic.CapabilityName,
		},
	)
	// 0.56.0 changes only the L1 frontend projection for existing Task,
	// EmailMessage, query, and record-view capabilities.
	crmReleaseCapabilities["0.56.0"] =
		slices.Clone(crmReleaseCapabilities["0.55.0"])
	// 0.57.0 adds an L1-only compatibility identity for authored Layout
	// action references. It introduces no Runtime or handler capability.
	crmReleaseCapabilities["0.57.0"] =
		slices.Clone(crmReleaseCapabilities["0.56.0"])
	// 0.58.0 extends the existing neutral SEND_MAIL route with recipient
	// classes and HTML. It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.58.0"] =
		slices.Clone(crmReleaseCapabilities["0.57.0"])
	// 0.59.0 makes CampaignMember relationship keys explicitly immutable
	// after create. It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.59.0"] =
		slices.Clone(crmReleaseCapabilities["0.58.0"])
	// 0.60.0 removes the synthetic Quote/Standard RecordType and lets the
	// create-quote flow fall back to the platform's Master record type. It
	// introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.60.0"] =
		slices.Clone(crmReleaseCapabilities["0.59.0"])
	// 0.61.0 prevents message mutation while a send request owns its stable
	// request identity. It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.61.0"] =
		slices.Clone(crmReleaseCapabilities["0.60.0"])
	// 0.62.0 preserves request identity by canonical email draft across
	// retries. It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.62.0"] =
		slices.Clone(crmReleaseCapabilities["0.61.0"])
	// 0.63.0 removes CampaignMember relationship write protection so the
	// existing standard Lead conversion flow can relink membership records.
	// It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.63.0"] =
		slices.Clone(crmReleaseCapabilities["0.62.0"])
	// 0.64.0 adds source-to-target prefill declarations to the existing Lead
	// conversion action. It introduces no new Runtime or handler capability.
	crmReleaseCapabilities["0.64.0"] =
		slices.Clone(crmReleaseCapabilities["0.63.0"])
	// 0.65.0 publishes the existing retained object and field projection used
	// by the L1 AppService and frontend. It introduces no new Runtime or handler
	// capability.
	crmReleaseCapabilities["0.65.0"] =
		slices.Clone(crmReleaseCapabilities["0.64.0"])
	// 0.66.0 adds intent-bound mail attachments and versions the Runtime-owned
	// action handshake so older Runtimes fail closed. It consumes the existing
	// outbound-intent service plus its deploy-time attachment-delivery feature
	// marker and introduces no new L1 service handler.
	crmReleaseCapabilities["0.66.0"] =
		slices.Clone(crmReleaseCapabilities["0.65.0"])
	// 0.67.0 cuts every CRM permission authoring caller over to one L1
	// projection backed by the Runtime-owned five-family aggregate.
	crmReleaseCapabilities["0.67.0"] = append(
		slices.Clone(crmReleaseCapabilities["0.66.0"]),
		releaseCapability{
			capabilityID:      crmPermissionAuthoringCapability,
			handlerRef:        crmPermissionAuthoringHandlerRef,
			runtimeCapability: permissionauthoringsemantic.CapabilityName,
			additionalRuntimeCapabilities: []string{
				permissionauthoringsemantic.RecordTypeObjectGroupsCapabilityName,
			},
		},
	)
	// 0.68.0 declares the complete Catalog dependency used by the Permission
	// Authoring field projection. 0.67.0 remains immutable history.
	crmReleaseCapabilities["0.68.0"] = slices.Clone(crmReleaseCapabilities["0.67.0"])
	for index := range crmReleaseCapabilities["0.68.0"] {
		if crmReleaseCapabilities["0.68.0"][index].capabilityID != crmPermissionAuthoringCapability {
			continue
		}
		requirements := append(
			slices.Clone(crmReleaseCapabilities["0.68.0"][index].additionalRuntimeCapabilities),
			catalogsemantic.AppMetadataV3CapabilityName,
		)
		slices.Sort(requirements)
		crmReleaseCapabilities["0.68.0"][index].additionalRuntimeCapabilities = requirements
	}
	// 0.69.0 replaces mutable Layout administration with neutral durable
	// authoring and exact publication readback. Older release bytes stay fixed.
	crmReleaseCapabilities["0.69.0"] = append(slices.Clone(crmReleaseCapabilities["0.68.0"]), releaseCapability{
		capabilityID: crmLayoutAuthoringCapability, handlerRef: crmLayoutAuthoringHandlerRef,
		runtimeCapability: layoutauthoringsemantic.CapabilityName,
	})
	// 0.70.0 temporarily retires mutable Layout administration while its
	// activation boundary is redesigned. PageLayout resolution remains on the
	// existing Runtime capability and continues to serve record pages.
	crmReleaseCapabilities["0.70.0"] = slices.DeleteFunc(
		slices.Clone(crmReleaseCapabilities["0.69.0"]),
		func(capability releaseCapability) bool {
			return capability.capabilityID == crmLayoutAuthoringCapability
		},
	)
	// 0.71.0 changes only pack-owned physical indexing metadata.
	crmReleaseCapabilities["0.71.0"] = slices.Clone(crmReleaseCapabilities["0.70.0"])
	// 0.72.0 restores Layout administration on the synchronous Runtime-owned
	// workspace. Ordinary edits no longer publish a Catalog release.
	crmReleaseCapabilities["0.72.0"] = append(slices.Clone(crmReleaseCapabilities["0.71.0"]), releaseCapability{
		capabilityID: crmLayoutAuthoringCapability, handlerRef: crmLayoutAuthoringHandlerRef,
		runtimeCapability: layoutauthoringv2semantic.CapabilityName,
	})
	// Navigation is current Runtime configuration. The admin handler owns the
	// workload selection; UI Apps declares its use of that same capability.
	crmReleaseCapabilities["0.73.0"] = append(slices.Clone(crmReleaseCapabilities["0.72.0"]), releaseCapability{
		capabilityID:      crmApplicationNavigationCapability,
		handlerRef:        crmApplicationNavigationHandlerRef,
		runtimeCapability: navigationsemantic.CapabilityName,
	})
	for index := range crmReleaseCapabilities["0.73.0"] {
		capability := &crmReleaseCapabilities["0.73.0"][index]
		if capability.capabilityID == crmUIAppsCapability {
			capability.additionalRuntimeCapabilities = append(slices.Clone(capability.additionalRuntimeCapabilities), navigationsemantic.CapabilityName)
			slices.Sort(capability.additionalRuntimeCapabilities)
		}
	}

	// Search scope and search results consume the same effective object metadata.
	// Retire the separate scope handler whose only distinct serving dependency
	// was the legacy V1 AppMetadata view; do not retain a false V1 read grant.
	crmReleaseCapabilities["0.74.0"] = slices.DeleteFunc(slices.Clone(crmReleaseCapabilities["0.73.0"]), func(capability releaseCapability) bool {
		return capability.capabilityID == crmSearchScopeOrderCapability
	})
	for index := range crmReleaseCapabilities["0.74.0"] {
		capability := &crmReleaseCapabilities["0.74.0"][index]
		if capability.capabilityID == crmSearchCapability {
			capability.additionalRuntimeCapabilities = append(slices.Clone(capability.additionalRuntimeCapabilities), permissionsemantic.CapabilityName)
			slices.Sort(capability.additionalRuntimeCapabilities)
			capability.additionalRuntimeCapabilities = slices.Compact(capability.additionalRuntimeCapabilities)
		}
	}

	// Field creation semantics are an application-wide host requirement, declared
	// in crmConsumerRuntimeRequirements. They do not add a callable dependency
	// or change Catalog's selection of the existing handler routes.
	crmReleaseCapabilities["0.75.0"] = slices.Clone(crmReleaseCapabilities["0.74.0"])

}

var crmLegacyComponentKinds = map[string]string{
	"autonumber_counter":            "metadata.pack.autonumber-counter.v1",
	"autonumber_format":             "metadata.pack.autonumber-format.v1",
	"connected_app":                 "metadata.pack.connected-app.v1",
	"custom_permission":             "metadata.pack.custom-permission.v1",
	"field_mapping":                 "metadata.pack.field-mapping.v1",
	"flow_action_call_handler":      "metadata.pack.flow-action-call-handler.v1",
	"folder_type":                   "metadata.pack.folder-type.v1",
	"formula_function_bundle":       "metadata.pack.formula-function-bundle.v1",
	"href_rewrite_fields":           "metadata.pack.href-rewrite-fields.v1",
	"invocable_action":              "metadata.pack.invocable-action.v1",
	"list_fields":                   "metadata.pack.list-fields.v1",
	"oauth_custom_scope":            "metadata.pack.oauth-custom-scope.v1",
	"object_lifecycle_policy":       "metadata.pack.object-lifecycle-policy.v1",
	"object_record_mutation_policy": "metadata.pack.object-record-mutation-policy.v1",
	"object_spec":                   "metadata.pack.object-spec.v1",
	"page_layout":                   "metadata.pack.page-layout.v1",
	"permission":                    "metadata.pack.permission.v1",
	"record_type":                   "metadata.pack.record-type.v1",
	"related_list_rule":             "metadata.pack.related-list-rule.v1",
	"report_type":                   "metadata.pack.report-type.v1",
	"search_scope_order":            "metadata.pack.search-scope-order.v1",
	"standard_flow":                 "metadata.pack.standard-flow.v1",
	"ui_action":                     "metadata.pack.ui-action.v1",
	"validation_rule":               "metadata.pack.validation-rule.v1",
}

type legacyPackManifest struct {
	ManifestFormatVersion string                 `json:"manifestFormatVersion"`
	Name                  string                 `json:"name"`
	DisplayName           string                 `json:"displayName"`
	Category              string                 `json:"category"`
	Version               string                 `json:"version"`
	PlatformCompatibility legacyCompatibility    `json:"platformCompatibility"`
	Dependencies          []legacyPackDependency `json:"dependencies"`
	HostRequirements      []legacyRequirement    `json:"hostRequirements"`
	Components            []legacyPackComponent  `json:"components"`
}

type legacyPackDependency struct {
	Name              string `json:"name"`
	VersionConstraint string `json:"versionConstraint"`
}

type legacyCompatibility struct {
	Minimum          string `json:"min"`
	MaximumExclusive string `json:"maxExclusive"`
}

type legacyRequirement struct {
	Kind string `json:"kind"`
	Name string `json:"name"`
}

type legacyPackComponent struct {
	Kind          string               `json:"kind"`
	BusinessKey   string               `json:"businessKey"`
	Manageability string               `json:"manageability"`
	Path          string               `json:"path"`
	DependsOn     []legacyComponentRef `json:"dependsOn,omitempty"`
}

type legacyComponentRef struct {
	Kind        string `json:"kind"`
	BusinessKey string `json:"businessKey"`
}

type sourceComponent struct {
	descriptor legacyPackComponent
	component  appauthority.Component
	behavior   []byte
}

func buildCRMRelease(packRoot string) (appauthority.VerifiedPackage, []byte, string, error) {
	manifestRaw, err := os.ReadFile(filepath.Join(packRoot, "pack.json"))
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	if err := strictjson.RejectDuplicateKeys(manifestRaw, "pack.json"); err != nil {
		return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("parse pack.json: %w", err)
	}
	var legacy legacyPackManifest
	decoder := json.NewDecoder(bytes.NewReader(manifestRaw))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&legacy); err != nil {
		return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("parse pack.json: %w", err)
	}
	if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
		return appauthority.VerifiedPackage{}, nil, "", errors.New("pack.json contains multiple JSON values")
	}
	if legacy.ManifestFormatVersion != "1" || legacy.Name != crmPackName || legacy.Category != "app-pack" || legacy.Version == "" {
		return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("pack.json is not the exact %s v1 AppPack input", crmPackName)
	}
	if err := validateLegacyPackDependencies(legacy.Dependencies); err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	if len(legacy.Components) == 0 {
		return appauthority.VerifiedPackage{}, nil, "", errors.New("pack.json declares no metadata components")
	}

	components := make([]sourceComponent, 0, len(legacy.Components))
	byLegacyKey := make(map[string]string, len(legacy.Components))
	seenPaths := make(map[string]struct{}, len(legacy.Components))
	for i, descriptor := range legacy.Components {
		kind, err := appComponentKind(descriptor.Kind, legacy.Version)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components[%d]: %w", i, err)
		}
		if descriptor.BusinessKey == "" || strings.TrimSpace(descriptor.BusinessKey) != descriptor.BusinessKey {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components[%d] has non-canonical businessKey", i)
		}
		cleanPath, err := canonicalSourcePath(descriptor.Path)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components[%d]: %w", i, err)
		}
		if _, duplicate := seenPaths[cleanPath]; duplicate {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components duplicate source path %q", cleanPath)
		}
		seenPaths[cleanPath] = struct{}{}
		sourceBehavior, err := os.ReadFile(filepath.Join(packRoot, filepath.FromSlash(cleanPath)))
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", err
		}
		if !json.Valid(sourceBehavior) {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("component %s/%s is not valid JSON", descriptor.Kind, descriptor.BusinessKey)
		}
		behavior, err := componentbehavior.CanonicalJSONV1(sourceBehavior)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("component %s/%s canonical behavior: %w", descriptor.Kind, descriptor.BusinessKey, err)
		}
		contentDigest, err := appauthority.ComponentDigest(kind, 1, behavior)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", err
		}
		overridePolicy, err := overridePolicy(descriptor.Manageability)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components[%d]: %w", i, err)
		}
		component := appauthority.Component{
			ComponentKind: kind, ComponentFormatVersion: 1, NamespaceID: crmAppID,
			BusinessKey: descriptor.BusinessKey, DefinitionVersion: "1", LocalDependencies: []string{},
			ExternalComponentRefs: []appauthority.ExternalComponentRef{}, OverridePolicy: overridePolicy,
			RemovalPolicy: appauthority.RemovalPolicyBlockIfReferencedOrOverridden, ContentDigest: contentDigest,
		}
		component.ComponentID = appauthority.ComponentID(component.NamespaceID, component.ComponentKind, component.BusinessKey)
		legacyKey := descriptor.Kind + "\x00" + descriptor.BusinessKey
		if _, duplicate := byLegacyKey[legacyKey]; duplicate {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("components duplicate ownership fact %s/%s", descriptor.Kind, descriptor.BusinessKey)
		}
		byLegacyKey[legacyKey] = component.ComponentID
		components = append(components, sourceComponent{descriptor: descriptor, component: component, behavior: behavior})
	}
	for _, commandSource := range runtimeFlowCommandSources(legacy.Version) {
		raw, err := os.ReadFile(filepath.Join(packRoot, commandSource.path))
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", err
		}
		behavior, err := componentbehavior.CanonicalJSONV1(raw)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf(
				"Runtime Flow command canonical behavior: %w",
				err,
			)
		}
		contentDigest, err := appauthority.ComponentDigest(
			crmRuntimeCommandComponent,
			1,
			behavior,
		)
		if err != nil {
			return appauthority.VerifiedPackage{}, nil, "", err
		}
		component := appauthority.Component{
			ComponentKind: crmRuntimeCommandComponent, ComponentFormatVersion: 1,
			NamespaceID: crmAppID, BusinessKey: commandSource.businessKey,
			DefinitionVersion: "1", LocalDependencies: []string{},
			ExternalComponentRefs: []appauthority.ExternalComponentRef{},
			OverridePolicy:        appauthority.OverridePolicyReplace,
			RemovalPolicy:         appauthority.RemovalPolicyBlockIfReferencedOrOverridden,
			ContentDigest:         contentDigest,
		}
		component.ComponentID = appauthority.ComponentID(
			component.NamespaceID,
			component.ComponentKind,
			component.BusinessKey,
		)
		components = append(components, sourceComponent{
			descriptor: legacyPackComponent{
				Kind:          "app_release_runtime_command",
				BusinessKey:   commandSource.businessKey,
				Manageability: "locked",
				Path:          commandSource.path,
			},
			component: component,
			behavior:  behavior,
		})
	}
	if legacy.Version != "0.9.0" && releaseIncludesRuntimeCapability(legacy.Version, attachmentsemantic.CapabilityName) {
		if err := validateRecordViewSourceClosure(components); err != nil {
			return appauthority.VerifiedPackage{}, nil, "", err
		}
	}
	for i := range components {
		dependencies := make([]string, 0, len(components[i].descriptor.DependsOn))
		for _, dependency := range components[i].descriptor.DependsOn {
			componentID, ok := byLegacyKey[dependency.Kind+"\x00"+dependency.BusinessKey]
			if !ok {
				return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("component %s/%s depends on undeclared %s/%s", components[i].descriptor.Kind, components[i].descriptor.BusinessKey, dependency.Kind, dependency.BusinessKey)
			}
			dependencies = append(dependencies, componentID)
		}
		slices.Sort(dependencies)
		if slices.ContainsFunc(dependencies, func(value string) bool { return value == components[i].component.ComponentID }) {
			return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("component %s/%s depends on itself", components[i].descriptor.Kind, components[i].descriptor.BusinessKey)
		}
		for j := 1; j < len(dependencies); j++ {
			if dependencies[j] == dependencies[j-1] {
				return appauthority.VerifiedPackage{}, nil, "", fmt.Errorf("component %s/%s has duplicate dependency", components[i].descriptor.Kind, components[i].descriptor.BusinessKey)
			}
		}
		components[i].component.LocalDependencies = dependencies
	}
	slices.SortFunc(components, func(a, b sourceComponent) int {
		return strings.Compare(a.component.ComponentID, b.component.ComponentID)
	})
	bundleComponents := make([]appauthority.Component, len(components))
	blobs := make([]appauthority.ComponentBlob, len(components))
	for i, source := range components {
		bundleComponents[i] = source.component
		blobs[i] = appauthority.ComponentBlob{ComponentID: source.component.ComponentID, CanonicalBehavior: source.behavior}
	}
	bundle := appauthority.MetadataBundle{SchemaVersion: appauthority.MetadataBundleSchemaVersion, Components: bundleComponents}
	requiredEngineCapabilities, providedCapabilities, err := appCapabilities(legacy.Version)
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	if consumerRequirements := crmConsumerRuntimeRequirements(legacy.Version); len(consumerRequirements) != 0 {
		requiredEngineCapabilities = append(
			requiredEngineCapabilities,
			consumerRequirements...,
		)
		slices.Sort(requiredEngineCapabilities)
		requiredEngineCapabilities =
			slices.Compact(requiredEngineCapabilities)
	}
	manifest := appauthority.AppManifest{
		SchemaVersion: appauthority.AppManifestSchemaVersion, AppID: crmAppID, PublisherID: crmPublisherID,
		SemanticVersion: legacy.Version, MetadataBundleDigest: zeroDigest(), Dependencies: []appauthority.Dependency{},
		OwnedNamespaces:            []string{crmAppID},
		RequiredProtocols:          []appauthority.ProtocolRequirement{{ProtocolID: "nexuscrm.runtime.v1", Major: 1, MinimumMinor: 0}},
		RequiredEngineCapabilities: requiredEngineCapabilities,
		ProvidedCapabilities:       providedCapabilities,
		FrontendCompatibility: appauthority.FrontendCompatibility{Mode: appauthority.FrontendModeRequired,
			HostSDK:            &appauthority.ProtocolRequirement{ProtocolID: "nexuscrm.frontend.host-sdk.v1", Major: 1, MinimumMinor: 0},
			ContributionSchema: &appauthority.ProtocolRequirement{ProtocolID: "nexuscrm.frontend.contribution.v1", Major: 1, MinimumMinor: 0}},
	}
	manifest.MetadataBundleDigest, err = appauthority.MetadataBundleDigest(bundle, manifest)
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	verified, err := appauthority.VerifyPackage(appauthority.Package{Manifest: manifest, Bundle: bundle, ComponentBlobs: blobs})
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	_, artifactRaw, err := releaseartifact.BuildPackage(verified.Package)
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	sourceDigest, err := sourceTreeDigest(packRoot, manifestRaw, components)
	if err != nil {
		return appauthority.VerifiedPackage{}, nil, "", err
	}
	return verified, artifactRaw, sourceDigest, nil
}

// validateLegacyPackDependencies distinguishes pack install order from Catalog
// App dependencies. The connector is installed independently by pack
// lifecycle, so it must not be emitted as an appauthority.Dependency.
func validateLegacyPackDependencies(values []legacyPackDependency) error {
	if len(values) == 0 {
		return nil
	}
	if len(values) == 1 &&
		values[0].Name == "gmail-connected-mail-connector" &&
		values[0].VersionConstraint == ">=0.1.0 <0.2.0" {
		return nil
	}
	return errors.New("CRM AppRelease builder requires explicit App dependency conversion before accepting legacy dependencies")
}

func runtimeFlowCommandSources(version string) []runtimeFlowCommandSource {
	v1 := runtimeFlowCommandSource{
		path:        crmRuntimeFlowCommandV1Source,
		businessKey: flowcommandsemantic.CommandKind,
	}
	switch version {
	case "0.15.0", "0.16.0", "0.17.0":
		return []runtimeFlowCommandSource{v1}
	case "0.18.0", "0.19.0", "0.20.0", "0.21.0", "0.22.0", "0.23.0", "0.24.0", "0.25.0", "0.26.0", "0.27.0", "0.28.0", "0.29.0", "0.30.0", "0.31.0", "0.32.0", "0.33.0", "0.34.0", "0.35.0", "0.36.0", "0.39.0", "0.40.0", "0.43.0":
		return []runtimeFlowCommandSource{
			v1,
			{
				path:        crmRuntimeFlowCommandV2Source,
				businessKey: flowcommandsemantic.CommandKind + "/v2",
			},
		}
	case "0.44.0", "0.45.0", "0.46.0", "0.47.0", "0.48.0", "0.49.0", "0.50.0", "0.51.0", "0.52.0", "0.53.0", "0.54.0", "0.55.0", "0.56.0", "0.57.0", "0.58.0", "0.59.0", "0.60.0", "0.61.0", "0.62.0", "0.63.0", "0.64.0", "0.65.0", "0.66.0", "0.67.0", "0.68.0", "0.69.0", "0.70.0", "0.71.0", "0.72.0", "0.73.0", "0.74.0", "0.75.0":
		return []runtimeFlowCommandSource{
			v1,
			{
				path:        crmRuntimeFlowCommandV2Source,
				businessKey: flowcommandsemantic.CommandKind + "/v2",
			},
			{
				path:        crmRuntimeFlowCommandV3Source,
				businessKey: flowcommandsemantic.CommandKind + "/v3",
			},
		}
	default:
		return nil
	}
}

// 0.15.0 published the immutable command registration but did not yet declare
// its consumer-only Runtime dependency. Preserve that historical AppRelease
// byte-for-byte. 0.16 closes command acceptance. 0.17 additionally declares
// the authorized IntegrationBindingView used before command acceptance.
func crmConsumerRuntimeRequirements(version string) []string {
	switch version {
	case "0.16.0":
		return []string{commandsemantic.CapabilityName}
	case "0.17.0", "0.18.0", "0.19.0", "0.20.0", "0.21.0", "0.22.0", "0.23.0", "0.24.0", "0.25.0":
		return []string{
			catalogsemantic.IntegrationBindingCapabilityName,
			commandsemantic.CapabilityName,
		}
	case "0.26.0", "0.27.0", "0.28.0", "0.29.0", "0.30.0", "0.31.0", "0.32.0", "0.33.0", "0.34.0", "0.35.0", "0.36.0", "0.39.0":
		return []string{
			catalogsemantic.IntegrationBindingCapabilityName,
			commandsemantic.CapabilityName,
			sessionsemantic.CapabilityName,
		}
	case "0.40.0":
		return []string{
			catalogsemantic.IntegrationBindingCapabilityName,
			commandsemantic.CapabilityName,
			crmMailboxAuthorizationRuntimeCapability,
			outboundintentsemantic.CapabilityName,
			sessionsemantic.CapabilityName,
		}
	case "0.43.0", "0.44.0", "0.45.0", "0.46.0", "0.47.0", "0.48.0", "0.49.0", "0.50.0", "0.51.0", "0.52.0", "0.53.0", "0.54.0", "0.55.0", "0.56.0", "0.57.0", "0.58.0", "0.59.0", "0.60.0", "0.61.0", "0.62.0", "0.63.0", "0.64.0", "0.65.0", "0.66.0", "0.67.0", "0.68.0", "0.69.0", "0.70.0", "0.71.0", "0.72.0", "0.73.0", "0.74.0", "0.75.0":
		result := []string{
			catalogsemantic.IntegrationBindingCapabilityName,
			commandsemantic.CapabilityName,
			crmMailboxAuthorizationRuntimeCapability,
			outboundintentsemantic.CapabilityName,
			sessionsemantic.CapabilityName,
		}
		if version == "0.51.0" || version == "0.52.0" || version == "0.53.0" || version == "0.54.0" || version == "0.55.0" || version == "0.56.0" || version == "0.57.0" || version == "0.58.0" || version == "0.59.0" || version == "0.60.0" || version == "0.61.0" || version == "0.62.0" || version == "0.63.0" || version == "0.64.0" || version == "0.65.0" || version == "0.66.0" || version == "0.67.0" || version == "0.68.0" || version == "0.69.0" || version == "0.70.0" || version == "0.71.0" || version == "0.72.0" || version == "0.73.0" || version == "0.74.0" || version == "0.75.0" {
			// PageLayout resolution is a shared delegated Runtime read used by
			// existing describe/layout and record-ui handlers. Those handler
			// declarations name the dependency, while this workload grant lets
			// both reuse one Runtime serving capability without inventing a
			// second L1 handler or route owner.
			result = append(result, pagelayoutsemantic.CapabilityName)
		}
		if version == "0.66.0" || version == "0.67.0" || version == "0.68.0" || version == "0.69.0" || version == "0.70.0" || version == "0.71.0" || version == "0.72.0" || version == "0.73.0" || version == "0.74.0" || version == "0.75.0" {
			// This marker proves the complete V3 path (enqueue, exact frozen
			// attachment stream, and Integration consumer operation set), not a
			// second serving capability.
			result = append(result, outboundintentsemantic.MailAttachmentDeliveryCapabilityName)
		}
		if version == "0.67.0" || version == "0.68.0" || version == "0.69.0" || version == "0.70.0" || version == "0.71.0" || version == "0.72.0" || version == "0.73.0" || version == "0.74.0" || version == "0.75.0" {
			// This is a semantic feature fence on the selected Permission
			// Authoring Runtime, never a second selectable serving capability.
			result = append(result, permissionauthoringsemantic.RecordTypeObjectGroupsCapabilityName)
		}
		if version == "0.75.0" {
			result = append(result, permissionsemantic.FieldCreateCapabilityName)
		}

		slices.Sort(result)
		return result
	default:
		return nil
	}
}

func validateRecordViewSourceClosure(components []sourceComponent) error {
	type objectField struct {
		APIName string `json:"apiName"`
	}
	type objectSpec struct {
		APIName string        `json:"apiName"`
		Fields  []objectField `json:"fields"`
	}
	type resolver struct {
		ResolverID                      string `json:"resolverId"`
		SourceObjectAPIName             string `json:"sourceObjectApiName"`
		SourceFileReferenceFieldAPIName string `json:"sourceFileReferenceFieldApiName"`
		SourceDeletedFieldAPIName       string `json:"sourceDeletedFieldApiName"`
	}

	objects := make(map[string]map[string]struct{})
	var resolvers []resolver
	for _, component := range components {
		switch component.descriptor.Kind {
		case "object_spec":
			var specs []objectSpec
			if err := json.Unmarshal(component.behavior, &specs); err != nil {
				return fmt.Errorf("record-view source closure parse object spec %s: %w", component.descriptor.BusinessKey, err)
			}
			for _, object := range specs {
				fields := make(map[string]struct{}, len(object.Fields))
				for _, field := range object.Fields {
					fields[field.APIName] = struct{}{}
				}
				objects[object.APIName] = fields
			}
		case "href_rewrite_fields":
			var document struct {
				Resolvers []resolver `json:"resolvers"`
			}
			if err := json.Unmarshal(component.behavior, &document); err != nil {
				return fmt.Errorf("record-view source closure parse resolver %s: %w", component.descriptor.BusinessKey, err)
			}
			resolvers = append(resolvers, document.Resolvers...)
		}
	}
	if len(resolvers) == 0 {
		return errors.New("record-view attachment capability has no source resolver")
	}
	for _, resolver := range resolvers {
		fields, ok := objects[resolver.SourceObjectAPIName]
		if !ok {
			return fmt.Errorf("record-view resolver %q source object %q is absent from the AppRelease object schema", resolver.ResolverID, resolver.SourceObjectAPIName)
		}
		if _, ok := fields[resolver.SourceFileReferenceFieldAPIName]; !ok {
			return fmt.Errorf("record-view resolver %q source field %s.%s is absent from the AppRelease object schema", resolver.ResolverID, resolver.SourceObjectAPIName, resolver.SourceFileReferenceFieldAPIName)
		}
		if resolver.SourceDeletedFieldAPIName != "IsDeleted" {
			if _, ok := fields[resolver.SourceDeletedFieldAPIName]; !ok {
				return fmt.Errorf("record-view resolver %q source field %s.%s is absent from the AppRelease object schema", resolver.ResolverID, resolver.SourceObjectAPIName, resolver.SourceDeletedFieldAPIName)
			}
		}
	}
	return nil
}

func appCapabilities(version string) ([]string, []appauthority.ProvidedCapability, error) {
	declarations, ok := crmReleaseCapabilities[version]
	if !ok {
		return nil, nil, fmt.Errorf("CRM AppRelease version %q has no reviewed capability mapping", version)
	}
	if _, closed := crmCatalogSelectionClosedVersions[version]; closed {
		if err := validateCatalogSelectionClosure(declarations); err != nil {
			return nil, nil, err
		}
		if _, deployable := crmCatalogDeployableVersions[version]; deployable {
			if err := validateCatalogDeployableClosure(
				declarations, crmConsumerRuntimeRequirements(version),
			); err != nil {
				return nil, nil, err
			}
		}
	}
	requiredSet := make(map[string]struct{}, len(declarations))
	provided := make([]appauthority.ProvidedCapability, 0, len(declarations))
	for _, declaration := range declarations {
		runtimeCapabilities := append(
			[]string{declaration.runtimeCapability},
			declaration.additionalRuntimeCapabilities...,
		)
		slices.Sort(runtimeCapabilities)
		for _, runtimeCapability := range runtimeCapabilities {
			requiredSet[runtimeCapability] = struct{}{}
		}
		provided = append(provided, appauthority.ProvidedCapability{
			CapabilityID:                declaration.capabilityID,
			Kind:                        appauthority.CapabilityKindServiceHandler,
			HandlerRef:                  declaration.handlerRef,
			RequiredRuntimeCapabilities: runtimeCapabilities,
		})
	}
	required := make([]string, 0, len(requiredSet))
	for runtimeCapability := range requiredSet {
		required = append(required, runtimeCapability)
	}
	slices.Sort(required)
	slices.SortFunc(provided, func(a, b appauthority.ProvidedCapability) int {
		return strings.Compare(a.CapabilityID, b.CapabilityID)
	})
	return required, provided, nil
}

func validateCatalogSelectionClosure(declarations []releaseCapability) error {
	selected := make(map[string]int, len(declarations))
	var assign func(int, map[string]struct{}) bool
	assign = func(index int, visited map[string]struct{}) bool {
		declaration := declarations[index]
		candidates := append(
			[]string{declaration.runtimeCapability},
			declaration.additionalRuntimeCapabilities...,
		)
		slices.Sort(candidates)
		candidates = slices.Compact(candidates)
		for _, capability := range candidates {
			if _, seen := visited[capability]; seen {
				continue
			}
			visited[capability] = struct{}{}
			previous, occupied := selected[capability]
			if !occupied || assign(previous, visited) {
				selected[capability] = index
				return true
			}
		}
		return false
	}
	for index, declaration := range declarations {
		if !assign(index, make(map[string]struct{})) {
			return fmt.Errorf(
				"CRM AppRelease handler %q has no unique Catalog serving selection",
				declaration.capabilityID,
			)
		}
	}
	return nil
}

func validateCatalogDeployableClosure(
	declarations []releaseCapability, consumerCapabilities []string,
) error {
	selected := make(map[string]struct{}, len(declarations))
	var assign func(int, map[string]struct{}) bool
	owners := make(map[string]int, len(declarations))
	consumer := make(map[string]struct{}, len(consumerCapabilities))
	for _, capability := range consumerCapabilities {
		consumer[capability] = struct{}{}
	}
	assign = func(index int, visited map[string]struct{}) bool {
		candidates := append(
			[]string{declarations[index].runtimeCapability},
			declarations[index].additionalRuntimeCapabilities...,
		)
		slices.Sort(candidates)
		candidates = slices.Compact(candidates)
		for _, capability := range candidates {
			// A workload consumer grant and a handler serving selection are two
			// different ownership modes. A shared delegated dependency may be
			// named by multiple handlers, but once the release declares it as
			// consumer-granted it must not consume any handler's unique serving
			// slot.
			if _, consumerGranted := consumer[capability]; consumerGranted &&
				capability != declarations[index].runtimeCapability {
				continue
			}
			if _, seen := visited[capability]; seen {
				continue
			}
			visited[capability] = struct{}{}
			previous, occupied := owners[capability]
			if !occupied || assign(previous, visited) {
				owners[capability] = index
				return true
			}
		}
		return false
	}
	for index := range declarations {
		if !assign(index, make(map[string]struct{})) {
			return fmt.Errorf(
				"CRM AppRelease handler %q has no unique Catalog serving selection",
				declarations[index].capabilityID,
			)
		}
	}
	for capability := range owners {
		selected[capability] = struct{}{}
	}
	for _, declaration := range declarations {
		dependencies := append(
			[]string{declaration.runtimeCapability},
			declaration.additionalRuntimeCapabilities...,
		)
		for _, capability := range dependencies {
			if _, served := selected[capability]; served {
				continue
			}
			if _, explicitlyConsumerOnly := consumer[capability]; explicitlyConsumerOnly {
				continue
			}
			return fmt.Errorf(
				"CRM AppRelease handler %q leaves Runtime capability %q without a serving selection or supported consumer-only owner",
				declaration.capabilityID, capability,
			)
		}
	}
	return nil
}

func canonicalSourcePath(path string) (string, error) {
	cleanPath := filepath.ToSlash(filepath.Clean(path))
	if cleanPath == "." || cleanPath != path || filepath.IsAbs(path) || strings.HasPrefix(cleanPath, "../") {
		return "", fmt.Errorf("path %q is not a canonical relative path", path)
	}
	return cleanPath, nil
}

func sourceTreeDigest(packRoot string, manifestRaw []byte, components []sourceComponent) (string, error) {
	type entry struct {
		path string
		raw  []byte
	}
	entries := []entry{{path: "pack.json", raw: manifestRaw}}
	for _, component := range components {
		path := component.descriptor.Path
		raw, err := os.ReadFile(filepath.Join(packRoot, filepath.FromSlash(path)))
		if err != nil {
			return "", err
		}
		entries = append(entries, entry{path: path, raw: raw})
	}
	slices.SortFunc(entries, func(a, b entry) int { return strings.Compare(a.path, b.path) })
	hash := sha256.New()
	for _, item := range entries {
		itemHash := sha256.Sum256(item.raw)
		_, _ = fmt.Fprintf(hash, "%x  ./%s\n", itemHash, item.path)
	}
	return "sha256:" + hex.EncodeToString(hash.Sum(nil)), nil
}

func appComponentKind(legacyKind, version string) (string, error) {
	kind, ok := crmLegacyComponentKinds[legacyKind]
	if !ok {
		return "", fmt.Errorf("legacy component kind %q has no reviewed CRM AppRelease v1 mapping", legacyKind)
	}
	if legacyKind == "href_rewrite_fields" && releaseIncludesRuntimeCapability(version, viewsemantic.CapabilityName) {
		// The legacy source kind remains the current in-process presentation
		// input. Starting with the record-view release, the immutable AppRelease
		// publishes the neutral Runtime fact consumed by Catalog instead. Prior
		// immutable releases retain their byte-identical component identity.
		return crmRecordViewComponent, nil
	}
	return kind, nil
}

func releaseIncludesRuntimeCapability(version, capability string) bool {
	for _, declaration := range crmReleaseCapabilities[version] {
		if declaration.runtimeCapability == capability {
			return true
		}
	}
	return false
}

func overridePolicy(manageability string) (string, error) {
	switch manageability {
	case "locked":
		return appauthority.OverridePolicyReplace, nil
	case "defaults_only":
		return appauthority.OverridePolicyPreserveAuthorized, nil
	default:
		return "", fmt.Errorf("unsupported manageability %q", manageability)
	}
}

func zeroDigest() string { return "sha256:" + strings.Repeat("0", 64) }
