package packs

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"slices"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/auth"
	"github.com/nexuscrm/db/pkg/schemashape"
	"github.com/nexuscrm/kernel/pkg/formula"
	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/nexuscrm/shared/pkg/searchmetadata"
	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
	"github.com/pingcap-inc/nexuscrm-controlplane/internal/offlinecandidateauthority"
	"github.com/pingcap-inc/nexuscrm-controlplane/specdrift"
)

func TestSourceBackedPackHashIncludesManifestDependencies(t *testing.T) {
	base := testSourceBackedPack(t, []PackDependency{}, nil)
	withDependency := testSourceBackedPack(t, []PackDependency{{
		Name:              "platform-addon",
		VersionConstraint: ">=1.0.0 <2.0.0",
	}}, nil)

	baseHash, err := base.PayloadHash()
	if err != nil {
		t.Fatalf("base payload hash: %v", err)
	}
	dependencyHash, err := withDependency.PayloadHash()
	if err != nil {
		t.Fatalf("dependency payload hash: %v", err)
	}
	if baseHash == dependencyHash {
		t.Fatalf("hash did not change when manifest dependencies changed: %s", baseHash)
	}
}

func TestSourceBackedPackHostRequirementsDigestIsIndependentFromPayloadHash(t *testing.T) {
	base := testSourceBackedPack(t, nil, nil)
	withHostRequirement := testSourceBackedPack(t, nil, nil)
	withHostRequirement.manifest.HostRequirements = []HostPrimitiveRequirement{{
		Kind: "renderer",
		Name: "app-pack:jira/action-renderer/transition",
	}}

	basePayloadHash, err := base.PayloadHash()
	if err != nil {
		t.Fatalf("base payload hash: %v", err)
	}
	requirementPayloadHash, err := withHostRequirement.PayloadHash()
	if err != nil {
		t.Fatalf("host requirement payload hash: %v", err)
	}
	if basePayloadHash != requirementPayloadHash {
		t.Fatalf("payload hash changed when only host requirements changed:\nbase=%s\nwithHostRequirement=%s", basePayloadHash, requirementPayloadHash)
	}

	baseHostDigest, err := PackHostRequirementsDigest(base)
	if err != nil {
		t.Fatalf("base host requirements digest: %v", err)
	}
	requirementHostDigest, err := PackHostRequirementsDigest(withHostRequirement)
	if err != nil {
		t.Fatalf("host requirement digest: %v", err)
	}
	if baseHostDigest == requirementHostDigest {
		t.Fatalf("host requirements digest did not change when manifest hostRequirements changed: %s", baseHostDigest)
	}
	if !IsPackHostRequirementsDigestV1(requirementHostDigest) {
		t.Fatalf("host requirements digest = %q, want %s* format", requirementHostDigest, PackHostRequirementsDigestV1Prefix)
	}
}

func TestSourceBackedPackHostRequirementsDigestCanonicalizesOrderAndKind(t *testing.T) {
	packA := testSourceBackedPack(t, nil, nil)
	packA.manifest.HostRequirements = []HostPrimitiveRequirement{
		{Kind: "Renderer", Name: "app-pack:jira/action-renderer/transition"},
		{Kind: "PRODUCT-DIALECT", Name: "salesforce-v63"},
	}
	packB := testSourceBackedPack(t, nil, nil)
	packB.manifest.HostRequirements = []HostPrimitiveRequirement{
		{Kind: "product-dialect", Name: "salesforce-v63"},
		{Kind: "renderer", Name: "app-pack:jira/action-renderer/transition"},
	}

	digestA, err := PackHostRequirementsDigest(packA)
	if err != nil {
		t.Fatalf("digest A: %v", err)
	}
	digestB, err := PackHostRequirementsDigest(packB)
	if err != nil {
		t.Fatalf("digest B: %v", err)
	}
	if digestA != digestB {
		t.Fatalf("host requirements digest is not canonical:\nA=%s\nB=%s", digestA, digestB)
	}
}

func TestResolveInstalledPackRejectsUnsupportedHostRequirement(t *testing.T) {
	resetDefaultHostPrimitivesForTest(t)
	root := writePackFixture(t, "jira-like-sample", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "object-spec:jira-like-sample",
      "manageability": "locked",
      "payload": [{"apiName": "Issue"}]
    }
  ]`, `"dependencies": [], "hostRequirements": [{"kind":"product-dialect","name":"salesforce-v63"}],`))

	_, err := ResolveInstalledPack(InstalledPackResolverInput{
		Name:                  "jira-like-sample",
		Category:              AppPack,
		ManifestFormatVersion: SupportedManifestFormatVersion,
		SourceKind:            PackSourceKindLocalPath,
		SourceURI:             root,
	}, InstalledPackResolverOptions{AllowBundledFallback: false})
	if err == nil {
		t.Fatal("ResolveInstalledPack = nil, want unsupported host requirement error")
	}
	if got := err.Error(); !strings.Contains(got, "host.unsupported") || !strings.Contains(got, "product-dialect/salesforce-v63") {
		t.Fatalf("ResolveInstalledPack error = %q, want product-dialect host.unsupported", got)
	}
}

func TestSourceBackedPackCanonicalizesComponentPayloadHash(t *testing.T) {
	payloadA := `[
  {"apiName":"Issue","label":"Issue","tableType":"business","category":"jira","sharingModel":"Private","enableHierarchySharing":false,"description":"Issue work item","fields":[{"apiName":"Subject","label":"Subject","type":"string","required":true,"isNameField":true}]}
	]`
	payloadB := `[
  {"fields":[{"isNameField":true,"required":true,"type":"string","label":"Subject","apiName":"Subject"}],"description":"Issue work item","enableHierarchySharing":false,"sharingModel":"Private","category":"jira","tableType":"business","label":"Issue","apiName":"Issue"}
]`
	packA := testSourceBackedPack(t, nil, map[string]string{"issue": payloadA})
	packB := testSourceBackedPack(t, nil, map[string]string{"issue": payloadB})

	hashA, err := packA.PayloadHash()
	if err != nil {
		t.Fatalf("payload A hash: %v", err)
	}
	hashB, err := packB.PayloadHash()
	if err != nil {
		t.Fatalf("payload B hash: %v", err)
	}
	if hashA != hashB {
		t.Fatalf("canonical-equivalent payloads produced different pack hashes:\nA=%s\nB=%s", hashA, hashB)
	}

	declaredA, err := packA.DeclaredComponents()
	if err != nil {
		t.Fatalf("declared A: %v", err)
	}
	declaredB, err := packB.DeclaredComponents()
	if err != nil {
		t.Fatalf("declared B: %v", err)
	}
	if declaredA[0].PayloadHash != declaredB[0].PayloadHash {
		t.Fatalf("canonical-equivalent payloads produced different declared hashes: %s vs %s", declaredA[0].PayloadHash, declaredB[0].PayloadHash)
	}
}

func TestSourceBackedPackTopologicalSortIsDeterministic(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				testObjectSpecManifestComponent("z-task", "Task"),
				testObjectSpecManifestComponent("a-issue", "Issue"),
			},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("construct source pack: %v", err)
	}
	components := pack.Components()
	if got := components[0].BusinessKey(); got != "a-issue" {
		t.Fatalf("first component = %s, want lexical tie-break a-issue", got)
	}
}

func TestSourceBackedPackAcceptsValidationRuleInstall(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindValidationRule,
				BusinessKey:   "issue-required",
				Manageability: ManageabilityDefaultsOnly,
				Payload: []byte(`{"validationRules":[{
					"id":"vrule_issue_subject_required",
					"objectApiName":"Issue",
					"name":"Issue_Subject_Required",
					"active":true,
					"condition":"ISBLANK(Subject) && NOT(HasProject)",
					"errorMessage":"Subject is required",
					"recordSetBindings":[{
						"name":"HasProject",
						"mode":"exists",
						"objectApiName":"Project",
						"filters":[
							{"field":"Id","operator":"equals","valueSource":"recordPath","value":"ProjectId"}
						]
					}]
				}]}`),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack validation_rule: %v", err)
	}
	if got := len(pack.Components()); got != 1 {
		t.Fatalf("components = %d, want 1", got)
	}
}

func TestSourceBackedPackAcceptsObjectRecordMutationPolicyInstall(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectRecordMutationPolicy,
				BusinessKey:   "Issue",
				Manageability: ManageabilityLocked,
				Payload:       []byte(`{"objectApiName":"Issue","recordMutation":{"deny":[{"surfaces":["singleCreate"],"message":"closed"}]}}`),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack object_record_mutation_policy: %v", err)
	}
	if got := len(pack.Components()); got != 1 {
		t.Fatalf("components = %d, want 1", got)
	}
	component := pack.Components()[0]
	if component.Kind() != ComponentKindObjectRecordMutationPolicy || component.BusinessKey() != "Issue" {
		t.Fatalf("component = %s/%s, want object_record_mutation_policy/Issue", component.Kind(), component.BusinessKey())
	}

	plan, err := pack.InstallPlan()
	if err != nil {
		t.Fatalf("InstallPlan: %v", err)
	}
	components := plan.ComponentsForPhase(InstallPlanPhaseObjectMetadata)
	found := false
	for _, component := range components {
		if component.Kind() == ComponentKindObjectRecordMutationPolicy && component.BusinessKey() == "Issue" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("object metadata phase components = %#v, want object_record_mutation_policy/Issue", components)
	}
}

func TestSourceBackedPackAcceptsObjectLifecyclePolicyInstall(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectLifecyclePolicy,
				BusinessKey:   "Issue",
				Manageability: ManageabilityLocked,
				Payload:       []byte(`{"objectApiName":"Issue","lifecycle":{"hooks":[{"phase":"after_create_in_tx","capability":"jira.issue.lifecycle"}]}}`),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack object_lifecycle_policy: %v", err)
	}
	if got := len(pack.Components()); got != 1 {
		t.Fatalf("components = %d, want 1", got)
	}
	component := pack.Components()[0]
	if component.Kind() != ComponentKindObjectLifecyclePolicy || component.BusinessKey() != "Issue" {
		t.Fatalf("component = %s/%s, want object_lifecycle_policy/Issue", component.Kind(), component.BusinessKey())
	}

	plan, err := pack.InstallPlan()
	if err != nil {
		t.Fatalf("InstallPlan: %v", err)
	}
	components := plan.ComponentsForPhase(InstallPlanPhaseObjectMetadata)
	found := false
	for _, component := range components {
		if component.Kind() == ComponentKindObjectLifecyclePolicy && component.BusinessKey() == "Issue" {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("object metadata phase components = %#v, want object_lifecycle_policy/Issue", components)
	}
}

func TestSourceBackedPackRejectsObjectRecordMutationPolicyWithoutObjectSpecDependency(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				testObjectSpecManifestComponent("issue", "Issue"),
				{
					Kind:          ComponentKindObjectRecordMutationPolicy,
					BusinessKey:   "Issue",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"objectApiName":"Issue","recordMutation":{"deny":[{"surfaces":["singleCreate"],"message":"closed"}]}}`),
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil || !strings.Contains(err.Error(), "requires dependsOn on object_spec") {
		t.Fatalf("NewSourceBackedPack error = %v, want object_spec dependency rejection", err)
	}
}

func TestSourceBackedPackRejectsObjectLifecyclePolicyWithoutObjectSpecDependency(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				testObjectSpecManifestComponent("issue", "Issue"),
				{
					Kind:          ComponentKindObjectLifecyclePolicy,
					BusinessKey:   "Issue",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"objectApiName":"Issue","lifecycle":{"hooks":[{"phase":"after_create_in_tx","capability":"jira.issue.lifecycle"}]}}`),
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil || !strings.Contains(err.Error(), "requires dependsOn on object_spec") {
		t.Fatalf("NewSourceBackedPack error = %v, want object_spec dependency rejection", err)
	}
}

func TestSourceBackedPackRejectsObjectSpecRecordMutationPolicySurface(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityLocked,
					Payload: []byte(`[{
						"apiName":"Issue",
						"label":"Issue",
						"tableType":"standard_object",
						"category":"jira",
						"sharingModel":"Private",
						"enableHierarchySharing":false,
						"fields":[{"apiName":"Subject","label":"Subject","type":"string","required":true,"isNameField":true}],
						"accessPolicy":{"recordMutation":{"deny":[{"surfaces":["singleCreate"],"message":"closed"}]}}
						}]`),
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil || !strings.Contains(err.Error(), "cannot author accessPolicy.recordMutation") {
		t.Fatalf("NewSourceBackedPack error = %v, want object_spec recordMutation surface rejection", err)
	}
}

func TestSourceBackedPackRejectsObjectSpecLifecyclePolicySurface(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "issue",
				Manageability: ManageabilityLocked,
				Payload:       []byte(`[{"apiName":"Issue","label":"Issue","labelPlural":"Issues","sharingModel":"ReadWrite","accessPolicy":{"lifecycle":{"hooks":[{"phase":"after_create_in_tx","capability":"jira.issue.lifecycle"}]}},"fields":[{"apiName":"Name","type":"Text","label":"Name"}]}]`),
			}},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil || !strings.Contains(err.Error(), "object_lifecycle_policy") {
		t.Fatalf("NewSourceBackedPack error = %v, want lifecycle sidecar rejection", err)
	}
}

func TestSourceBackedObjectSpecComponentResolvesCrossComponentRollupSources(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "epic",
					Manageability: ManageabilityLocked,
					DependsOn: []ComponentReference{{
						Kind:        ComponentKindObjectSpec,
						BusinessKey: "story",
					}},
					Payload: []byte(testObjectSpecPayloadWithFields("Epic__c",
						`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "TotalPoints__c", "label": "Total Points", "type": "rollupsummary", "rollupConfig": {"summaryObject": "Story__c", "summaryField": "Points__c", "relationshipField": "EpicId__c", "calcType": "SUM"}}`,
					)),
				},
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "story",
					Manageability: ManageabilityLocked,
					Payload: []byte(testObjectSpecPayloadWithFields("Story__c",
						`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "Points__c", "label": "Points", "type": "double"}`,
						`{"apiName": "EpicId__c", "label": "Epic", "type": "masterdetail", "referenceTo": ["Epic__c"]}`,
					)),
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	var epicComponent *sourceBackedObjectSpecComponent
	for _, component := range pack.Components() {
		objectComponent, ok := component.(*sourceBackedObjectSpecComponent)
		if ok && objectComponent.BusinessKey() == "epic" {
			epicComponent = objectComponent
			break
		}
	}
	if epicComponent == nil {
		t.Fatal("epic object_spec component not found")
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs, err := convertSpecToTableDefinitionsWithResolver(
		epicComponent.specs,
		sm,
		epicComponent.fieldResolver(),
	)
	if err != nil {
		t.Fatalf("convertSpecToTableDefinitionsWithResolver: %v", err)
	}

	totalPoints := findColumnDefinition(t, defs[0].Columns, "TotalPoints__c")
	if totalPoints.Type != "DECIMAL(65,30)" {
		t.Fatalf("TotalPoints__c SQL type = %q, want DECIMAL(65,30)", totalPoints.Type)
	}
}

func TestSourceBackedObjectSpecComponentResolvesCrossComponentRollupSummarySources(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "initiative",
					Manageability: ManageabilityLocked,
					DependsOn: []ComponentReference{{
						Kind:        ComponentKindObjectSpec,
						BusinessKey: "epic",
					}},
					Payload: []byte(testObjectSpecPayloadWithFields("Initiative__c",
						`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "EpicId__c", "label": "Epic", "type": "masterdetail", "referenceTo": ["Epic__c"]}`,
						`{"apiName": "ProgramPoints__c", "label": "Program Points", "type": "rollupsummary", "rollupConfig": {"summaryObject": "Epic__c", "summaryField": "TotalPoints__c", "relationshipField": "InitiativeId__c", "calcType": "SUM"}}`,
					)),
				},
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "epic",
					Manageability: ManageabilityLocked,
					DependsOn: []ComponentReference{{
						Kind:        ComponentKindObjectSpec,
						BusinessKey: "story",
					}},
					Payload: []byte(testObjectSpecPayloadWithFields("Epic__c",
						`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "InitiativeId__c", "label": "Initiative", "type": "masterdetail", "referenceTo": ["Initiative__c"]}`,
						`{"apiName": "TotalPoints__c", "label": "Total Points", "type": "rollupsummary", "rollupConfig": {"summaryObject": "Story__c", "summaryField": "Points__c", "relationshipField": "EpicId__c", "calcType": "SUM"}}`,
					)),
				},
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "story",
					Manageability: ManageabilityLocked,
					Payload: []byte(testObjectSpecPayloadWithFields("Story__c",
						`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "Points__c", "label": "Points", "type": "double"}`,
						`{"apiName": "EpicId__c", "label": "Epic", "type": "masterdetail", "referenceTo": ["Epic__c"]}`,
					)),
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	var initiativeComponent *sourceBackedObjectSpecComponent
	for _, component := range pack.Components() {
		objectComponent, ok := component.(*sourceBackedObjectSpecComponent)
		if ok && objectComponent.BusinessKey() == "initiative" {
			initiativeComponent = objectComponent
			break
		}
	}
	if initiativeComponent == nil {
		t.Fatal("initiative object_spec component not found")
	}

	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	defs, err := convertSpecToTableDefinitionsWithResolver(
		initiativeComponent.specs,
		sm,
		initiativeComponent.fieldResolver(),
	)
	if err != nil {
		t.Fatalf("convertSpecToTableDefinitionsWithResolver: %v", err)
	}

	programPoints := findColumnDefinition(t, defs[0].Columns, "ProgramPoints__c")
	if programPoints.Type != "DECIMAL(65,30)" {
		t.Fatalf("ProgramPoints__c SQL type = %q, want DECIMAL(65,30)", programPoints.Type)
	}
}

func TestSourceBackedObjectSpecComponentDisplayNameValidationResolvesCrossComponentChild(t *testing.T) {
	pack := testSourceBackedPack(t, nil, map[string]string{
		"account": testObjectSpecPayloadWithFields("Account",
			`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "ExternalId__c", "label": "External ID", "type": "string"}`,
		),
		"widget": `[
  {
    "apiName": "Widget__c",
    "label": "Widget",
    "tableType": "standard_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Widget work item",
    "displayNameField": "Account.ExternalId__c",
    "fields": [
      {"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true},
      {"apiName": "AccountId", "label": "Account", "type": "reference", "referenceTo": ["Account"], "relationshipName": "Account"}
    ]
  }
]`,
	})

	var widgetComponent *sourceBackedObjectSpecComponent
	for _, component := range pack.Components() {
		objectComponent, ok := component.(*sourceBackedObjectSpecComponent)
		if ok && objectComponent.BusinessKey() == "widget" {
			widgetComponent = objectComponent
			break
		}
	}
	if widgetComponent == nil {
		t.Fatal("widget object_spec component not found")
	}

	if err := validateSpecDisplayNameFieldsWithResolver(widgetComponent.specs, widgetComponent.fieldResolver()); err != nil {
		t.Fatalf("validate display name fields through source-backed apply/verify resolver: %v", err)
	}
}

func TestSourceBackedObjectSpecComponentDisplayNameValidationResolvesFallbackSchema(t *testing.T) {
	pack := testSourceBackedPack(t, []PackDependency{{Name: "base-crm"}}, map[string]string{
		"widget": `[
  {
    "apiName": "Widget__c",
    "label": "Widget",
    "tableType": "standard_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Widget work item",
    "displayNameField": "Account.ExternalId__c",
    "fields": [
      {"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true},
      {"apiName": "AccountId", "label": "Account", "type": "reference", "referenceTo": ["Account"], "relationshipName": "Account"}
    ]
  }
]`,
	})

	widgetComponent := pack.components[0].(*sourceBackedObjectSpecComponent)
	accountSchema := &models.ObjectMetadata{
		APIName: "Account",
		Fields: []models.FieldMetadata{
			{APIName: "ExternalId__c", Type: constants.FieldTypeString},
		},
	}
	resolver := widgetComponent.fieldResolverWithFallback(func(apiName string) *models.ObjectMetadata {
		if strings.EqualFold(strings.TrimSpace(apiName), "Account") {
			return accountSchema
		}
		return nil
	})

	if err := validateSpecDisplayNameFieldsWithResolver(widgetComponent.specs, resolver); err != nil {
		t.Fatalf("validate display name fields through fallback schema resolver: %v", err)
	}
}

func TestCustomFieldComponentKindIsInstallable(t *testing.T) {
	if !DefaultSourceBackedComponentKindRegistry().Supports(ComponentKindCustomField) {
		t.Fatalf("source-backed kind registry does not support %q", ComponentKindCustomField)
	}

	root := writePackFixture(t, "custom-field-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "EscalationCode__c", "label": "Escalation Code", "type": "string"}`,
	)+`
    },
    {
      "kind": "custom_field",
      "businessKey": "Issue/EscalationCode__c",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"objectApiName":"Issue","fieldApiName":"EscalationCode__c"}
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected custom_field installable source:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}

	var found bool
	for _, component := range report.Pack.Components() {
		if component.Kind() == ComponentKindCustomField {
			found = true
			if got := component.BusinessKey(); got != "Issue/EscalationCode__c" {
				t.Fatalf("custom_field business key = %q, want %q", got, "Issue/EscalationCode__c")
			}
		}
	}
	if !found {
		t.Fatalf("installable pack components did not include %q", ComponentKindCustomField)
	}
}

func TestConnectorPushValidationOptionsIncludeCustomFieldDeclarations(t *testing.T) {
	specs := []ObjectSpec{{
		APIName: "WorkItem__c",
		Fields: []FieldSpec{{
			APIName:     "Parent__c",
			Type:        string(constants.FieldTypeReference),
			ReferenceTo: []string{"Parent__c"},
		}},
	}}
	customFields := []declaredCustomFieldExtension{{
		ObjectAPIName: "WorkItem__c",
		Field: FieldSpec{
			APIName:     "Parent__c",
			Type:        string(constants.FieldTypeReference),
			ReferenceTo: []string{"Parent__c"},
		},
	}, {
		ObjectAPIName: "WorkItem__c",
		Field: FieldSpec{
			APIName:     "Escalated_To__c",
			Type:        string(constants.FieldTypeReference),
			ReferenceTo: []string{"User"},
		},
	}}

	contracts := []connector.ConnectorPushDurableMetadataContract{{SourceObject: "WorkItem__c"}}
	opts := connectorPushValidationOptionsFromObjectSpecs(specs, customFields, contracts, func(apiName string) *models.ObjectMetadata {
		if apiName != "WorkItem__c" {
			return nil
		}
		return &models.ObjectMetadata{
			APIName: "WorkItem__c",
			Fields: []models.FieldMetadata{{
				APIName:     "Account__c",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Account"},
			}},
		}
	})
	fields := opts.ObjectFields["workitem__c"]
	if len(fields) != 3 {
		t.Fatalf("WorkItem__c fields = %#v, want object_spec field, unique custom_field declaration, and fallback schema field", fields)
	}
	var sawEscalated, sawFallback bool
	for _, field := range fields {
		if field.APIName == "Escalated_To__c" && field.Type == string(constants.FieldTypeReference) && slices.Equal(field.ReferenceTo, []string{"User"}) {
			sawEscalated = true
		}
		if field.APIName == "Account__c" && field.Type == string(constants.FieldTypeReference) && slices.Equal(field.ReferenceTo, []string{"Account"}) {
			sawFallback = true
		}
	}
	if !sawEscalated {
		t.Fatalf("custom_field declaration was not included in connector push validation fields: %#v", fields)
	}
	if !sawFallback {
		t.Fatalf("fallback schema field was not merged into partial connector push validation fields: %#v", fields)
	}
}

func TestConnectorPushValidationOptionsIncludeJunctionFallbackSchema(t *testing.T) {
	contracts := []connector.ConnectorPushDurableMetadataContract{{
		SourceObject: "WorkItem__c",
		Associations: []connector.ConnectorPushAssociationContract{{
			SourceObject: "RelatedItem__c",
			Junction: &connector.ConnectorPushAssociationJunctionContract{
				Object:            "WorkItemRelated__c",
				SourceLookupField: "WorkItem__c",
				TargetLookupField: "RelatedItem__c",
				Filters: []connector.ConnectorPushAssociationJunctionFilter{{
					Field:  "Primary__c",
					Equals: true,
				}},
				Limit: 1,
			},
		}},
	}}
	var requested []string
	opts := connectorPushValidationOptionsFromObjectSpecs(nil, nil, contracts, func(apiName string) *models.ObjectMetadata {
		requested = append(requested, apiName)
		if apiName != "WorkItemRelated__c" {
			return nil
		}
		return &models.ObjectMetadata{
			APIName: "WorkItemRelated__c",
			Fields: []models.FieldMetadata{
				{
					APIName:     "WorkItem__c",
					Type:        constants.FieldTypeReference,
					ReferenceTo: []string{"WorkItem__c"},
				},
				{
					APIName:     "RelatedItem__c",
					Type:        constants.FieldTypeReference,
					ReferenceTo: []string{"RelatedItem__c"},
				},
				{
					APIName: "Primary__c",
					Type:    constants.FieldTypeBoolean,
				},
			},
		}
	})
	if !slices.Contains(requested, "WorkItem__c") || !slices.Contains(requested, "WorkItemRelated__c") {
		t.Fatalf("fallback schema requested objects = %#v, want source and junction objects", requested)
	}
	fields := opts.ObjectFields["workitemrelated__c"]
	if len(fields) != 3 {
		t.Fatalf("WorkItemRelated__c fields = %#v, want junction fallback schema fields", fields)
	}
	var sawSourceLookup, sawTargetLookup, sawFilter bool
	for _, field := range fields {
		switch field.APIName {
		case "WorkItem__c":
			sawSourceLookup = field.Type == string(constants.FieldTypeReference) && slices.Equal(field.ReferenceTo, []string{"WorkItem__c"})
		case "RelatedItem__c":
			sawTargetLookup = field.Type == string(constants.FieldTypeReference) && slices.Equal(field.ReferenceTo, []string{"RelatedItem__c"})
		case "Primary__c":
			sawFilter = field.Type == string(constants.FieldTypeBoolean)
		}
	}
	if !sawSourceLookup || !sawTargetLookup || !sawFilter {
		t.Fatalf("junction fallback schema fields = %#v", fields)
	}
}

func TestResolveInstalledPackUsesObjectSchemaResolverForConnectorPushAssociationLookup(t *testing.T) {
	root := writePackFixture(t, "connector-installed-resolver", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "crm",
      "manageability": "locked",
      "path": "objects/crm.json"
    },
    {"kind":"standard_flow","businessKey":"Opportunity_To_Remote","manageability":"defaults_only","payload":{"start":{"object":"Opportunity"},"actionCalls":[{"name":"PushOpportunity","actionType":"PushToConnector"}]}},
    {"kind":"standard_flow","businessKey":"Account_To_Remote","manageability":"defaults_only","payload":{"start":{"object":"Account"},"actionCalls":[{"name":"PushAccount","actionType":"PushToConnector"}]}},
    {"kind":"connector_field_mapping","businessKey":"jira-like-sample/push-mappings","manageability":"locked","payload":[
      {"sourceObject":"Opportunity","targetObject":"remoteDeal","actionType":"PushToConnector","direction":"outbound","mappings":[{"sourceField":"Name","targetField":"dealname"}]},
      {"sourceObject":"Account","targetObject":"remoteCompany","actionType":"PushToConnector","direction":"outbound","mappings":[{"sourceField":"Name","targetField":"name"}]}
    ]},
    {"kind":"connector_push_contract","businessKey":"jira-like-sample/Account_To_Remote/PushAccount/Account/remote-company","manageability":"locked","payload":{
      "flowDeveloperName":"Account_To_Remote",
      "flowActionName":"PushAccount",
      "slotLineageId":"account-remote-sync",
      "sourceObject":"Account",
      "destinationSlotId":"remote-company",
      "profileOwnerPack":"hubspot-connector",
      "profileName":"default",
      "targetObject":"remoteCompany",
      "createPathTemplate":"/crm/v3/objects/companies",
      "updatePathTemplate":"/crm/v3/objects/companies/{remoteId}",
      "responseIdPath":"id",
      "remoteIdField":"RemoteCompanyId__c"
    }},
    {"kind":"connector_push_contract","businessKey":"jira-like-sample/Opportunity_To_Remote/PushOpportunity/Opportunity/remote-deal","manageability":"locked","payload":{
      "flowDeveloperName":"Opportunity_To_Remote",
      "flowActionName":"PushOpportunity",
      "slotLineageId":"opportunity-remote-sync",
      "sourceObject":"Opportunity",
      "destinationSlotId":"remote-deal",
      "profileOwnerPack":"hubspot-connector",
      "profileName":"default",
      "targetObject":"remoteDeal",
      "createPathTemplate":"/crm/v3/objects/deals",
      "updatePathTemplate":"/crm/v3/objects/deals/{remoteId}",
      "responseIdPath":"id",
      "remoteIdField":"RemoteDealId__c",
      "associations":[{
        "sourceLookupField":"AccountId",
        "sourceObject":"Account",
        "remoteIdField":"RemoteCompanyId__c",
        "targetActionSlotId":"account-remote-sync",
        "targetDestinationSlotId":"remote-company",
        "targetObject":"remoteCompany",
        "payloadField":"associations",
        "payloadMergeMode":"append",
        "inlinePhases":["create"],
        "payloadTemplate":{"to":{"id":"{toId}"}}
      }]
    }}
  ]`, `"dependencies": [],`))
	writeFile(t, root, "objects/crm.json", `[
  {
    "apiName": "Account",
    "label": "Account",
    "tableType": "standard_object",
    "category": "crm",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Account",
    "fields": [
      {"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true},
      {"apiName": "RemoteCompanyId__c", "label": "Remote Company ID", "type": "string"}
    ]
  },
  {
    "apiName": "Opportunity",
    "label": "Opportunity",
    "tableType": "standard_object",
    "category": "crm",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "Opportunity",
    "fields": [
      {"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true},
      {"apiName": "RemoteDealId__c", "label": "Remote Deal ID", "type": "string"}
    ]
  }
]`)
	resolver := func(apiName string) *models.ObjectMetadata {
		if strings.TrimSpace(apiName) != "Opportunity" {
			return nil
		}
		return &models.ObjectMetadata{
			APIName: "Opportunity",
			Fields: []models.FieldMetadata{{
				APIName:     "AccountId",
				Type:        constants.FieldTypeReference,
				ReferenceTo: []string{"Account"},
			}},
		}
	}
	resolved, err := ResolveInstalledPack(
		InstalledPackResolverInput{
			Name:                  "jira-like-sample",
			Category:              AppPack,
			ManifestFormatVersion: ManifestFormatVersion2,
			SourceKind:            string(PackSourceKindLocalPath),
			SourceURI:             root,
		},
		InstalledPackResolverOptions{AllowBundledFallback: false, ObjectSchemaResolver: resolver},
	)
	if err != nil {
		t.Fatalf("ResolveInstalledPack rejected resolver-backed connector_push association lookup: %v", err)
	}
	sourceBacked, ok := resolved.(*SourceBackedPack)
	if !ok {
		t.Fatalf("ResolveInstalledPack returned %T, want *SourceBackedPack", resolved)
	}
	businessKey := "jira-like-sample/Opportunity_To_Remote/PushOpportunity/Opportunity/remote-deal"
	assertConnectorPushAssociationProfileProjection(
		t, sourceBacked, businessKey, "hubspot-connector", "default",
	)

	source, diagnostics := ResolvePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if len(diagnostics) != 0 {
		t.Fatalf("ResolvePackSourceWithOptions diagnostics: %#v", diagnostics)
	}
	stopped, err := NewSourceBackedPack(
		source,
		withConnectorPushValidationSchemaResolver(resolver),
		WithStoppedSourceSetApplyOnly(offlinecandidateauthority.NewStoppedSourceSetAuthority()),
	)
	if err != nil {
		t.Fatalf("NewSourceBackedPack stopped source set: %v", err)
	}
	assertConnectorPushAssociationProfileProjection(
		t, stopped, businessKey, "hubspot-connector", "default",
	)

	mismatched := cloneResolvedPackSource(source)
	mutated := false
	for index := range mismatched.Manifest.Components {
		component := &mismatched.Manifest.Components[index]
		if component.Kind != ComponentKindConnectorPushContract || component.BusinessKey != businessKey {
			continue
		}
		payload := string(component.Payload)
		replaced := strings.Replace(payload, `"profileName":"default"`, `"profileName":"deal-write"`, 1)
		if replaced == payload {
			t.Fatalf("connector push payload %q did not contain the expected profile", businessKey)
		}
		component.Payload = json.RawMessage(replaced)
		mutated = true
	}
	if !mutated {
		t.Fatalf("connector push component %q is missing", businessKey)
	}
	if _, err := NewSourceBackedPack(
		mismatched,
		WithStoppedSourceSetApplyOnly(offlinecandidateauthority.NewStoppedSourceSetAuthority()),
	); err == nil || !strings.Contains(err.Error(), "target profile must match source contract profile") {
		t.Fatalf("stopped source-set profile mismatch error = %v, want target-profile incompatibility", err)
	}
}

func assertConnectorPushAssociationProfileProjection(
	t *testing.T,
	pack *SourceBackedPack,
	businessKey string,
	wantOwner string,
	wantName string,
) {
	t.Helper()
	var declared *connector.ConnectorPushDurableMetadataContract
	for _, contract := range pack.ConnectorPushContractDeclarations() {
		if JoinComponentBusinessKey(
			contract.ConnectorOwnerKey,
			contract.FlowDeveloperName,
			contract.FlowActionName,
			contract.SourceObject,
			contract.DestinationSlotID,
		) == businessKey {
			copy := contract
			declared = &copy
			break
		}
	}
	if declared == nil || len(declared.Associations) != 1 {
		t.Fatalf("connector push declaration %q = %#v, want one association", businessKey, declared)
	}
	assertAssociationProfile := func(surface string, association connector.ConnectorPushAssociationContract) {
		t.Helper()
		if association.TargetProfileOwnerPack != wantOwner || association.TargetProfileName != wantName {
			t.Fatalf(
				"%s association target profile = %q/%q, want %q/%q",
				surface,
				association.TargetProfileOwnerPack,
				association.TargetProfileName,
				wantOwner,
				wantName,
			)
		}
	}
	assertAssociationProfile("declaration", declared.Associations[0])

	persisted, err := BuildPersistedRuntimeDeclaration(pack)
	if err != nil {
		t.Fatalf("BuildPersistedRuntimeDeclaration: %v", err)
	}
	rows, err := unmarshalRuntimeStartupShallowDeclaredRows(persisted.StartupShallowDeclaredRowsJSON())
	if err != nil {
		t.Fatalf("unmarshalRuntimeStartupShallowDeclaredRows: %v", err)
	}
	for _, row := range rows {
		if row.Kind != ComponentKindConnectorPushContract || row.BusinessKey != businessKey {
			continue
		}
		var projected connector.ConnectorPushDurableMetadataContract
		if err := json.Unmarshal([]byte(row.Projection), &projected); err != nil {
			t.Fatalf("decode connector push projection %q: %v", businessKey, err)
		}
		if len(projected.Associations) != 1 {
			t.Fatalf("projection %q associations = %#v, want one", businessKey, projected.Associations)
		}
		assertAssociationProfile("persisted projection", projected.Associations[0])
		return
	}
	t.Fatalf("persisted connector push projection %q is missing", businessKey)
}

func TestConnectorPushValidationSchemaResolverForSourceUsesLiveSchemaForContractObjects(t *testing.T) {
	ownerPack := "salesforce-crm-standard"
	source := &ResolvedPackSource{Manifest: &PackManifest{
		Name: "pingcap-hubspot-companion",
		Dependencies: []PackDependency{{
			Name: ownerPack,
		}},
		Components: []ManifestComponent{{
			Kind: ComponentKindConnectorPushContract,
			Payload: json.RawMessage(`{
				"sourceObject":"Opportunity",
				"associations":[{
					"junction":{"object":"OpportunityContactRole"}
				}]
			}`),
		}},
	}}
	resolver := connectorPushValidationSchemaResolverForSource(source, PackSourceOptions{
		ObjectSchemaResolver: func(apiName string) *models.ObjectMetadata {
			switch apiName {
			case "Opportunity":
				return &models.ObjectMetadata{APIName: apiName, Fields: []models.FieldMetadata{{
					APIName:   "AccountId",
					OwnerKind: constants.FieldOwnerKindRuntime,
				}}}
			case "OpportunityContactRole":
				return &models.ObjectMetadata{APIName: apiName, Fields: []models.FieldMetadata{{
					APIName:   "ContactId",
					OwnerKind: constants.FieldOwnerKindRuntime,
				}}}
			default:
				return nil
			}
		},
	}, true)
	for _, apiName := range []string{"Opportunity", "OpportunityContactRole"} {
		schema := resolver(apiName)
		if schema == nil || len(schema.Fields) != 1 {
			t.Fatalf("resolver schema for declared contract object %q = %#v, want live fallback field", apiName, schema)
		}
	}
	if schema := resolver("Account"); schema != nil {
		t.Fatalf("resolver schema for undeclared object = %#v, want nil", schema)
	}
}

func TestOAuthCustomScopeComponentKindIsInstallable(t *testing.T) {
	if !DefaultSourceBackedComponentKindRegistry().Supports(ComponentKindOAuthCustomScope) {
		t.Fatalf("source-backed kind registry does not support %q", ComponentKindOAuthCustomScope)
	}
	capability, ok := DefaultSourceBackedComponentCapabilityRegistry().CapabilityFor(ComponentKindOAuthCustomScope)
	if !ok || !capability.Installable || !capability.SupportsManageability(ManageabilityLocked) {
		t.Fatalf("oauth_custom_scope capability = %#v, ok=%v; want installable locked support", capability, ok)
	}

	root := writePackFixture(t, "oauth-scope-pack", validPackManifest(`[
    {
      "kind": "oauth_custom_scope",
      "businessKey": "external.records.read",
      "manageability": "locked",
      "payload": {
        "id": "oauthscope_external_records_read",
        "name": "external.records.read",
        "description": "Read-only access to an app-pack records REST API for public OAuth clients.",
        "allowed_client_types": ["public"],
        "allowed_methods": ["GET", "HEAD"],
        "route_family_prefix": "/services/data/", "route_patterns": ["/services/data", "/services/data/:version/sobjects"],
        "resource_links": ["sobjects"],
        "active": true
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected oauth_custom_scope installable source:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}

	var found bool
	for _, component := range report.Pack.Components() {
		if component.Kind() == ComponentKindOAuthCustomScope {
			found = true
			if got := component.BusinessKey(); got != "external.records.read" {
				t.Fatalf("oauth_custom_scope business key = %q, want external.records.read", got)
			}
		}
	}
	if !found {
		t.Fatalf("installable pack components did not include %q", ComponentKindOAuthCustomScope)
	}
}

func TestConnectedAppComponentKindIsInstallable(t *testing.T) {
	if !DefaultSourceBackedComponentKindRegistry().Supports(ComponentKindConnectedApp) {
		t.Fatalf("source-backed kind registry does not support %q", ComponentKindConnectedApp)
	}
	capability, ok := DefaultSourceBackedComponentCapabilityRegistry().CapabilityFor(ComponentKindConnectedApp)
	if !ok || !capability.Installable || !capability.SupportsManageability(ManageabilityLocked) {
		t.Fatalf("connected_app capability = %#v, ok=%v; want installable locked support", capability, ok)
	}

	root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": {
        "id": "synthetic-client",
        "name": "Synthetic Client",
        "consumerKey": "synthetic-client-key",
        "consumerSecret": "synthetic-secret",
        "callbackUrl": "https://client.example/callback",
        "redirectUris": ["https://client.example/callback"],
        "clientType": "confidential",
        "scopes": ["api"],
        "isActive": true,
        "tokenEndpointAuthMethod": "client_secret_post",
        "allowedGrantTypes": ["authorization_code", "password"],
        "consentPolicy": "trusted",
        "consentVersion": 1,
        "requirePkce": false,
        "issueRefreshTokens": false,
        "permittedUsers": "AllUsers"
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected connected_app installable source:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}

	var found bool
	for _, component := range report.Pack.Components() {
		if component.Kind() == ComponentKindConnectedApp {
			found = true
			if got := component.BusinessKey(); got != "synthetic-client" {
				t.Fatalf("connected_app business key = %q, want synthetic-client", got)
			}
		}
	}
	if !found {
		t.Fatalf("installable pack components did not include %q", ComponentKindConnectedApp)
	}
}

func TestConnectedAppComponentAcceptsPublicScopeDeclaredBySameSource(t *testing.T) {
	root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "oauth_custom_scope",
      "businessKey": "external.records.read",
      "manageability": "locked",
      "payload": `+connectedAppPublicScopeManifestPayload()+`
    },
    {
      "kind": "connected_app",
      "businessKey": "synthetic-public-client",
      "manageability": "locked",
      "payload": `+connectedAppPublicComponentManifestPayload()+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected public connected_app backed by same-source scope:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}
}

func TestConnectedAppComponentRejectsPublicScopeWithoutSameSourcePolicy(t *testing.T) {
	root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-public-client",
      "manageability": "locked",
      "payload": `+connectedAppPublicComponentManifestPayload()+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted public connected_app without same-source public scope policy")
	}
	if got := report.Error(); !strings.Contains(got, "active public scope policy") {
		t.Fatalf("diagnostics = %s, want active public scope policy rejection", got)
	}
}

func TestConnectedAppComponentRejectsMissingRuntimeReadPolicyField(t *testing.T) {
	payload := strings.Replace(connectedAppComponentManifestPayload(), `"callbackUrl": "https://client.example/callback",`, "", 1)
	root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": `+payload+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted connected_app without callbackUrl")
	}
	if got := report.Error(); !strings.Contains(got, "callbackUrl is required") {
		t.Fatalf("diagnostics = %s, want callbackUrl required", got)
	}
}

func TestConnectedAppComponentRejectsDeferredRuntimePaths(t *testing.T) {
	cases := []struct {
		name string
		edit func(string) string
		want string
	}{
		{
			name: "client_credentials",
			edit: func(payload string) string {
				return strings.Replace(payload, `"allowedGrantTypes": ["authorization_code", "password"]`, `"allowedGrantTypes": ["authorization_code", "client_credentials"]`, 1)
			},
			want: "client_credentials",
		},
		{
			name: "run_as_user_id",
			edit: func(payload string) string {
				return strings.Replace(payload, `"permittedUsers": "AllUsers"`, `"runAsUserId": "005-system-user", "permittedUsers": "AllUsers"`, 1)
			},
			want: "RunAsUserId",
		},
		{
			name: "admin_approved",
			edit: func(payload string) string {
				return strings.Replace(payload, `"permittedUsers": "AllUsers"`, `"permittedUsers": "AdminApproved"`, 1)
			},
			want: "AdminApproved",
		},
		{
			name: "refresh_tokens",
			edit: func(payload string) string {
				return strings.Replace(payload, `"issueRefreshTokens": false`, `"issueRefreshTokens": true`, 1)
			},
			want: "refresh tokens",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": `+tc.edit(connectedAppComponentManifestPayload())+`
    }
  ]`, `"dependencies": [],`))

			report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
			if !report.HasErrors() {
				t.Fatalf("ValidateInstallablePackSourceWithOptions accepted deferred runtime path %s", tc.name)
			}
			if got := report.Error(); !strings.Contains(got, tc.want) {
				t.Fatalf("diagnostics = %s, want %q", got, tc.want)
			}
		})
	}
}

func TestConnectedAppComponentRejectsClientTypeSpecificSecretAndSessionShape(t *testing.T) {
	cases := []struct {
		name string
		edit func(string) string
		want string
	}{
		{
			name: "public_with_secret",
			edit: func(payload string) string {
				return strings.Replace(payload, `"clientType": "confidential"`, `"clientType": "public"`, 1)
			},
			want: "consumerSecret is forbidden for public clients",
		},
		{
			name: "public_with_session_issuer_class",
			edit: func(payload string) string {
				payload = strings.Replace(payload, `"consumerSecret": "synthetic-secret",`, "", 1)
				payload = strings.Replace(payload, `"clientType": "confidential"`, `"clientType": "public"`, 1)
				return strings.Replace(payload, `"permittedUsers": "AllUsers"`, `"permittedUsers": "AllUsers", "sessionIssuerClass": "synthetic-session"`, 1)
			},
			want: "sessionIssuerClass is forbidden for public clients",
		},
		{
			name: "confidential_without_secret",
			edit: func(payload string) string {
				return strings.Replace(payload, `"consumerSecret": "synthetic-secret",`, "", 1)
			},
			want: "consumerSecret is required for confidential clients",
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": `+tc.edit(connectedAppComponentManifestPayload())+`
    }
  ]`, `"dependencies": [],`))

			report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
			if !report.HasErrors() {
				t.Fatalf("ValidateInstallablePackSourceWithOptions accepted invalid client-specific shape %s", tc.name)
			}
			if got := report.Error(); !strings.Contains(got, tc.want) {
				t.Fatalf("diagnostics = %s, want %q", got, tc.want)
			}
		})
	}
}

func TestConnectedAppComponentSessionIssuerClassRequiresOwningPack(t *testing.T) {
	restore := auth.SetProductOAuthSessionIssuerClassOwners([]auth.ProductOAuthSessionIssuerClassOwner{{
		Class:          "synthetic_session",
		OwningAppPacks: []string{"jira-like-sample"},
	}})
	t.Cleanup(restore)

	withSessionClass := strings.Replace(
		connectedAppComponentManifestPayload(),
		`"permittedUsers": "AllUsers"`,
		`"permittedUsers": "AllUsers", "sessionIssuerClass": "synthetic-session"`,
		1,
	)
	root := writePackFixture(t, "oauth-client-pack", validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": `+withSessionClass+`
    }
  ]`, `"dependencies": [],`))
	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected same-pack session issuer:\n%s", report.Error())
	}

	foreignManifest := strings.Replace(validPackManifest(`[
    {
      "kind": "connected_app",
      "businessKey": "synthetic-client",
      "manageability": "locked",
      "payload": `+withSessionClass+`
    }
  ]`, `"dependencies": [],`), `"name": "jira-like-sample"`, `"name": "foreign-client-pack"`, 1)
	foreignRoot := writePackFixture(t, "foreign-client-pack", foreignManifest)
	foreignReport := ValidateInstallablePackSourceWithOptions("local_path:"+foreignRoot, testPackSourceOptions(t))
	if !foreignReport.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted foreign-pack session issuer class")
	}
	if got := foreignReport.Error(); !strings.Contains(got, "authoring app pack") {
		t.Fatalf("diagnostics = %s, want authoring app pack ownership error", got)
	}
}

func TestRecordTypeCodeComponentKindStaysOutOfSourceBackedContract(t *testing.T) {
	if DefaultSourceBackedComponentKindRegistry().Supports(ComponentKindRecordTypeCode) {
		t.Fatalf("source-backed kind registry supports %q; record type code is runtime materialization state", ComponentKindRecordTypeCode)
	}
	if _, ok := ComponentKindContractFor(ComponentKindRecordTypeCode); ok {
		t.Fatalf("component-kind contracts include %q; record type code must not become an installable package kind", ComponentKindRecordTypeCode)
	}
	if _, ok := DefaultSourceBackedComponentCapabilityRegistry().CapabilityFor(ComponentKindRecordTypeCode); ok {
		t.Fatalf("source-backed capabilities include %q; record type code must stay out of the public package contract", ComponentKindRecordTypeCode)
	}
	root := writePackFixture(t, "record-type-code-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "contract",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Contract",
		`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
	)+`
    },
    {
      "kind": "record_type",
      "businessKey": "record_types",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"contract"}],
      "payload": {"recordTypes":[{"objectApiName":"Contract","types":[{"name":"Master Agreement"}]}]}
    },
    {
      "kind": "record_type_code",
      "businessKey": "Contract/record_type_codes",
      "manageability": "locked",
      "dependsOn": [{"kind":"record_type","businessKey":"record_types"}],
      "payload": [{"objectApiName":"Contract","name":"Master Agreement","code":"MA"}]
    }
  ]`, `"dependencies": [],`))

	report := ValidatePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	assertDiagnostic(t, report, "component.kind.unsupported")
}

func TestWorkflowAlertComponentKindStaysOutOfSourceBackedContract(t *testing.T) {
	workflowAlertKind := ComponentKind("workflow_alert")

	if DefaultSourceBackedComponentKindRegistry().Supports(workflowAlertKind) {
		t.Fatalf("source-backed kind registry supports %q; WorkflowAlert is migration input, not durable pack-authored notification behavior", workflowAlertKind)
	}
	if _, ok := ComponentKindContractFor(workflowAlertKind); ok {
		t.Fatalf("component-kind contracts include %q; WorkflowAlert must not become an installable package kind", workflowAlertKind)
	}
	if _, ok := DefaultSourceBackedComponentCapabilityRegistry().CapabilityFor(workflowAlertKind); ok {
		t.Fatalf("source-backed capabilities include %q; WorkflowAlert must stay out of the public package contract", workflowAlertKind)
	}
	if _, ok := appPackInstallableComponentKinds()[workflowAlertKind]; ok {
		t.Fatalf("app-pack installability includes %q; notification behavior must be authored as Flow sendEmail metadata", workflowAlertKind)
	}

	root := writePackFixture(t, "workflow-alert-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
	)+`
    },
    {
      "kind": "workflow_alert",
      "businessKey": "Issue/New_Issue_Email",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"objectApiName":"Issue","template":"Welcome","recipients":[]}
    }
  ]`, `"dependencies": [],`))

	report := ValidatePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	assertDiagnostic(t, report, "component.kind.unsupported")
}

func TestRecordTypeComponentAcceptsInlineCode(t *testing.T) {
	root := writePackFixture(t, "record-type-code-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "document",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Document",
		`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
	)+`
    },
    {
      "kind": "record_type",
      "businessKey": "record_types",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"document"}],
      "payload": {"recordTypes":[{"objectApiName":"Document","types":[{"name":"Master Agreement","code":"MA"}]}]}
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected record_type inline code:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}
}

func TestRecordTypePicklistValueComponentKindIsInstallable(t *testing.T) {
	if !DefaultSourceBackedComponentKindRegistry().Supports(ComponentKindRecordTypePicklistValue) {
		t.Fatalf("source-backed kind registry does not support %q", ComponentKindRecordTypePicklistValue)
	}

	root := writePackFixture(t, "rt-picklist-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "Status", "label": "Status", "type": "picklist", "options": [{"value":"New","label":"New"},{"value":"Done","label":"Done"}]}`,
	)+`
    },
    {
      "kind": "record_type",
      "businessKey": "record_types",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"recordTypes":[{"objectApiName":"Issue","types":[{"name":"Bug"}]}]}
    },
    {
      "kind": "record_type_picklist_value",
      "businessKey": "Issue/Bug/Status",
      "manageability": "defaults_only",
      "dependsOn": [
        {"kind":"object_spec","businessKey":"issue"},
        {"kind":"record_type","businessKey":"record_types"}
      ],
      "payload": {
        "objectApiName":"Issue",
        "recordTypeName":"Bug",
        "fieldApiName":"Status",
        "values":[
          {"value":"New","default":true,"sortOrder":10},
          {"value":"Done","active":true,"sortOrder":20}
        ]
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected record_type_picklist_value source:\n%s", report.Error())
	}
	if report.Pack == nil {
		t.Fatal("ValidateInstallablePackSourceWithOptions did not construct installable pack")
	}

	var found bool
	for _, component := range report.Pack.Components() {
		if component.Kind() == ComponentKindRecordTypePicklistValue {
			found = true
			if got := component.BusinessKey(); got != "Issue/Bug/Status" {
				t.Fatalf("record_type_picklist_value business key = %q, want %q", got, "Issue/Bug/Status")
			}
		}
	}
	if !found {
		t.Fatalf("installable pack components did not include %q", ComponentKindRecordTypePicklistValue)
	}
}

func TestRecordTypePicklistValueComponentRejectsUnknownValue(t *testing.T) {
	root := writePackFixture(t, "rt-picklist-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "Status", "label": "Status", "type": "picklist", "options": [{"value":"New","label":"New"}]}`,
	)+`
    },
    {
      "kind": "record_type",
      "businessKey": "record_types",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"recordTypes":[{"objectApiName":"Issue","types":[{"name":"Bug"}]}]}
    },
    {
      "kind": "record_type_picklist_value",
      "businessKey": "Issue/Bug/Status",
      "manageability": "defaults_only",
      "dependsOn": [
        {"kind":"object_spec","businessKey":"issue"},
        {"kind":"record_type","businessKey":"record_types"}
      ],
      "payload": {
        "objectApiName":"Issue",
        "recordTypeName":"Bug",
        "fieldApiName":"Status",
        "values":[{"value":"Missing","sortOrder":10}]
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted unknown scoped value")
	}
	if !strings.Contains(report.Error(), `value "Missing"`) {
		t.Fatalf("error = %q, want missing value diagnostic", report.Error())
	}
}

// TestRecordTypePicklistValueLiveProjectionAcceptsPreservedL2Overlay is the
// Codex round-3 HIGH regression guard on the verify side. When the guarded
// UPSERT leaves an ownerless customized row (OwnerPack="", IsCustomized=1)
// untouched, live projection verification must accept that as valid converged
// state rather than rejecting it as an OwnerPack mismatch. The follow-up
// surviving-rows scan already filters on IsCustomized=0 so the customized row
// does not show up as undeclared either.
func TestRecordTypePicklistValueLiveProjectionAcceptsPreservedL2Overlay(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	expected := []sourceBackedRecordTypePicklistValueRow{{
		ObjectAPIName:  "Issue",
		RecordTypeName: "Bug",
		FieldAPIName:   "Status",
		Value:          "New",
		IsActive:       true,
		IsDefault:      false,
		SortOrder:      10,
		OwnerPack:      "pack",
	}}
	// The per-value lookup returns the preserved L2 overlay row.
	mock.ExpectQuery("SELECT o.ApiName, rt.Name, f.ApiName").
		WithArgs("Issue", "Bug", "Status", "New").
		WillReturnRows(sqlmock.NewRows([]string{
			"ObjectApiName", "RecordTypeName", "FieldApiName", "Value", "IsActive", "IsDefault", "SortOrder", "IsCustomized", "IsDeleted", "OwnerPack",
		}).AddRow("Issue", "Bug", "Status", "New", true, false, 10, true, false, ""))
	// The owner-pack surviving-rows query filters IsCustomized=0, so the
	// preserved L2 row is not returned and there are no undeclared survivors.
	mock.ExpectQuery("SELECT rtpv.").
		WithArgs("Issue", "Bug", "Status", "pack").
		WillReturnRows(sqlmock.NewRows([]string{"Value"}))

	if _, err := liveSourceBackedRecordTypePicklistValueProjection(context.Background(), VerifyDeps{DB: db}, expected, "pack", "test-source"); err != nil {
		t.Fatalf("live projection on preserved L2 overlay = %v, want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

// TestRecordTypePicklistValueLiveProjectionRejectsOwnerlessNonCustomized is
// the negative control: an ownerless row with IsCustomized=0 means the
// guarded UPSERT failed to claim it (the OwnerPack IF clause flips "" to
// VALUES(OwnerPack) when IsCustomized=0). Verify must fail closed instead
// of accepting that as a converged overlay.
func TestRecordTypePicklistValueLiveProjectionRejectsOwnerlessNonCustomized(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	expected := []sourceBackedRecordTypePicklistValueRow{{
		ObjectAPIName:  "Issue",
		RecordTypeName: "Bug",
		FieldAPIName:   "Status",
		Value:          "New",
		IsActive:       true,
		IsDefault:      false,
		SortOrder:      10,
		OwnerPack:      "pack",
	}}
	mock.ExpectQuery("SELECT o.ApiName, rt.Name, f.ApiName").
		WithArgs("Issue", "Bug", "Status", "New").
		WillReturnRows(sqlmock.NewRows([]string{
			"ObjectApiName", "RecordTypeName", "FieldApiName", "Value", "IsActive", "IsDefault", "SortOrder", "IsCustomized", "IsDeleted", "OwnerPack",
		}).AddRow("Issue", "Bug", "Status", "New", true, false, 10, false, false, ""))

	if _, err := liveSourceBackedRecordTypePicklistValueProjection(context.Background(), VerifyDeps{DB: db}, expected, "pack", "test-source"); err == nil {
		t.Fatal("live projection = nil err on ownerless non-customized row, want OwnerPack mismatch")
	} else if !strings.Contains(err.Error(), `OwnerPack = ""`) {
		t.Fatalf("error = %v, want empty-owner mismatch diagnostic", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestRecordTypePicklistValueLiveProjectionDetectsOrphan(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	expected := []sourceBackedRecordTypePicklistValueRow{{
		ObjectAPIName:  "Issue",
		RecordTypeName: "Bug",
		FieldAPIName:   "Status",
		Value:          "New",
		IsActive:       true,
		IsDefault:      true,
		SortOrder:      10,
		OwnerPack:      "pack",
	}}
	mock.ExpectQuery("SELECT o.ApiName, rt.Name, f.ApiName").
		WithArgs("Issue", "Bug", "Status", "New").
		WillReturnRows(sqlmock.NewRows([]string{
			"ObjectApiName", "RecordTypeName", "FieldApiName", "Value", "IsActive", "IsDefault", "SortOrder", "IsCustomized", "IsDeleted", "OwnerPack",
		}).AddRow("Issue", "Bug", "Status", "New", true, true, 10, false, false, "pack"))
	mock.ExpectQuery("SELECT rtpv.").
		WithArgs("Issue", "Bug", "Status", "pack").
		WillReturnRows(sqlmock.NewRows([]string{"Value"}).AddRow("New").AddRow("Orphan"))

	_, err = liveSourceBackedRecordTypePicklistValueProjection(context.Background(), VerifyDeps{DB: db}, expected, "pack", "test-source")
	if err == nil {
		t.Fatal("live projection error = nil, want orphan drift")
	}
	if !strings.Contains(err.Error(), "Orphan") {
		t.Fatalf("error = %v, want orphan value", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestCustomFieldComponentKindRejectsMissingObjectSpecDependency(t *testing.T) {
	root := writePackFixture(t, "custom-field-pack-missing-dep", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "EscalationCode__c", "label": "Escalation Code", "type": "string"}`,
	)+`
    },
    {
      "kind": "custom_field",
      "businessKey": "Issue/EscalationCode__c",
      "manageability": "locked",
      "payload": {"objectApiName":"Issue","fieldApiName":"EscalationCode__c"}
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions = nil errors, want missing dependsOn rejection")
	}
	if !strings.Contains(report.Error(), `requires dependsOn on object_spec "issue"`) {
		t.Fatalf("error = %v, want missing dependsOn rejection", report.Error())
	}
}

func TestCustomFieldComponentKindRejectsDefaultsOnlyObjectSpecBacking(t *testing.T) {
	root := writePackFixture(t, "custom-field-pack-defaults-only", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "defaults_only",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "EscalationCode__c", "label": "Escalation Code", "type": "string"}`,
	)+`
    },
    {
      "kind": "custom_field",
      "businessKey": "Issue/EscalationCode__c",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"objectApiName":"Issue","fieldApiName":"EscalationCode__c"}
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions = nil errors, want defaults_only backing rejection")
	}
	if !strings.Contains(report.Error(), `requires locked object_spec backing`) {
		t.Fatalf("error = %v, want defaults_only backing rejection", report.Error())
	}
}

func TestNewSourceBackedPackRejectsHrefRewriteFieldNotDeclaredByObjectSpec(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/href-field-missing",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "href-field-missing-pack",
			DisplayName:           "Href Field Missing Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityLocked,
					Payload: []byte(testObjectSpecPayloadWithFields("Issue",
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
					)),
				},
				{
					Kind:          ComponentKindHrefRewriteFields,
					BusinessKey:   "issue-file-hrefs",
					Manageability: ManageabilityLocked,
					DependsOn:     []ComponentReference{{Kind: ComponentKindObjectSpec, BusinessKey: "issue"}},
					Payload:       []byte(`{"fields":[{"parentApiName":"Issue","fieldApiName":"FileLinks__c"}]}`),
				},
			},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack accepted href_rewrite_fields for missing field, want rejection")
	}
	if got := err.Error(); !strings.Contains(got, "href_rewrite_fields") || !strings.Contains(got, "Issue/FileLinks__c") {
		t.Fatalf("error = %v, want href_rewrite_fields missing field rejection for Issue/FileLinks__c", got)
	}
}

func TestDeclaredCustomFieldExtension(t *testing.T) {
	pack := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "EscalationCode__c", "label": "Escalation Code", "type": "string"}`,
		),
	})
	specs := pack.components[0].(*sourceBackedObjectSpecComponent).specs

	t.Run("exact declared custom field match", func(t *testing.T) {
		declared, err := resolveDeclaredCustomFieldExtension(specs, "Issue", "EscalationCode__c")
		if err != nil {
			t.Fatalf("resolveDeclaredCustomFieldExtension: %v", err)
		}
		if declared.ObjectAPIName != "Issue" || declared.Field.APIName != "EscalationCode__c" {
			t.Fatalf("declared custom field = %#v, want Issue/EscalationCode__c", declared)
		}
	})

	t.Run("undeclared field rejected", func(t *testing.T) {
		_, err := resolveDeclaredCustomFieldExtension(specs, "Issue", "MissingField__c")
		if err == nil {
			t.Fatal("resolveDeclaredCustomFieldExtension = nil err, want undeclared-field rejection")
		}
		if !strings.Contains(err.Error(), "not declared") {
			t.Fatalf("error = %v, want undeclared-field rejection", err)
		}
	})

	t.Run("non-custom field rejected", func(t *testing.T) {
		_, err := resolveDeclaredCustomFieldExtension(specs, "Issue", "Subject")
		if err == nil {
			t.Fatal("resolveDeclaredCustomFieldExtension = nil err, want non-custom-field rejection")
		}
		if !strings.Contains(err.Error(), "custom field") {
			t.Fatalf("error = %v, want non-custom-field rejection", err)
		}
	})
}

func TestCustomFieldComponentVerifyRejectsMissingFieldRow(t *testing.T) {
	customField := testSourceBackedCustomFieldComponent(t)

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT o\\.ApiName, f\\.ApiName, COALESCE\\(f\\.OwnerKind, ''\\), COALESCE\\(f\\.OwnerPack, ''\\), COALESCE\\(f\\.IsCustomized, 0\\) FROM _System_Field f JOIN _System_Object o ON o\\.Id = f\\.ObjectId WHERE o\\.ApiName = \\? AND f\\.ApiName = \\? LIMIT 1").
		WithArgs("Issue", "EscalationCode__c").
		WillReturnRows(sqlmock.NewRows([]string{"ApiName", "ApiName", "OwnerKind", "OwnerPack", "IsCustomized"}))

	err = verifyPackComponents(context.Background(), VerifyDeps{DB: db}, []PackComponent{customField})
	if err == nil {
		t.Fatal("verifyPackComponents custom_field = nil, want missing-field error")
	}
	if !strings.Contains(err.Error(), "custom field Issue.EscalationCode__c is missing") {
		t.Fatalf("verifyPackComponents custom_field error = %v, want missing-field rejection", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestCustomFieldComponentVerifyRejectsWrongOwner(t *testing.T) {
	customField := testSourceBackedCustomFieldComponent(t)

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT o\\.ApiName, f\\.ApiName, COALESCE\\(f\\.OwnerKind, ''\\), COALESCE\\(f\\.OwnerPack, ''\\), COALESCE\\(f\\.IsCustomized, 0\\) FROM _System_Field f JOIN _System_Object o ON o\\.Id = f\\.ObjectId WHERE o\\.ApiName = \\? AND f\\.ApiName = \\? LIMIT 1").
		WithArgs("Issue", "EscalationCode__c").
		WillReturnRows(sqlmock.NewRows([]string{"ApiName", "ApiName", "OwnerKind", "OwnerPack", "IsCustomized"}).
			AddRow("Issue", "EscalationCode__c", "pack", "other-pack", 0))

	err = verifyPackComponents(context.Background(), VerifyDeps{DB: db}, []PackComponent{customField})
	if err == nil {
		t.Fatal("verifyPackComponents custom_field = nil, want owner mismatch error")
	}
	if !strings.Contains(err.Error(), `projection = packs.customFieldProjection`) {
		t.Fatalf("verifyPackComponents custom_field error = %v, want owner mismatch rejection", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestCustomFieldComponentApplyFailsClosedUntilDeclaredFieldExists(t *testing.T) {
	customField := testSourceBackedCustomFieldComponent(t)

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT o\\.ApiName, f\\.ApiName, COALESCE\\(f\\.OwnerKind, ''\\), COALESCE\\(f\\.OwnerPack, ''\\), COALESCE\\(f\\.IsCustomized, 0\\) FROM _System_Field f JOIN _System_Object o ON o\\.Id = f\\.ObjectId WHERE o\\.ApiName = \\? AND f\\.ApiName = \\? LIMIT 1").
		WithArgs("Issue", "EscalationCode__c").
		WillReturnRows(sqlmock.NewRows([]string{"ApiName", "ApiName", "OwnerKind", "OwnerPack", "IsCustomized"}))

	err = applyPackComponents(context.Background(), ApplyDeps{DB: db}, []PackComponent{customField})
	if err == nil {
		t.Fatal("applyPackComponents custom_field = nil, want missing-field error")
	}
	if !strings.Contains(err.Error(), "custom field Issue.EscalationCode__c is missing") {
		t.Fatalf("applyPackComponents custom_field error = %v, want missing-field rejection", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestCustomFieldComponentApplyAcceptsPackOwnedDeclaredField(t *testing.T) {
	customField := testSourceBackedCustomFieldComponent(t)

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("SELECT o\\.ApiName, f\\.ApiName, COALESCE\\(f\\.OwnerKind, ''\\), COALESCE\\(f\\.OwnerPack, ''\\), COALESCE\\(f\\.IsCustomized, 0\\) FROM _System_Field f JOIN _System_Object o ON o\\.Id = f\\.ObjectId WHERE o\\.ApiName = \\? AND f\\.ApiName = \\? LIMIT 1").
		WithArgs("Issue", "EscalationCode__c").
		WillReturnRows(sqlmock.NewRows([]string{"ApiName", "ApiName", "OwnerKind", "OwnerPack", "IsCustomized"}).
			AddRow("Issue", "EscalationCode__c", "pack", "jira-like-sample", 0))

	if err := applyPackComponents(context.Background(), ApplyDeps{DB: db}, []PackComponent{customField}); err != nil {
		t.Fatalf("applyPackComponents custom_field = %v, want nil", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func testSourceBackedCustomFieldComponent(t *testing.T) PackComponent {
	t.Helper()

	root := writePackFixture(t, "custom-field-pack-verify", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "EscalationCode__c", "label": "Escalation Code", "type": "string"}`,
	)+`
    },
    {
      "kind": "custom_field",
      "businessKey": "Issue/EscalationCode__c",
      "manageability": "locked",
      "dependsOn": [{"kind":"object_spec","businessKey":"issue"}],
      "payload": {"objectApiName":"Issue","fieldApiName":"EscalationCode__c"}
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if report.HasErrors() {
		t.Fatalf("ValidateInstallablePackSourceWithOptions rejected custom_field installable source:\n%s", report.Error())
	}
	for _, component := range report.Pack.Components() {
		if component.Kind() == ComponentKindCustomField {
			return component
		}
	}
	t.Fatal("custom_field component missing from installable pack")
	return nil
}

// TestSourceBackedPackAcceptsObjectSpecDefaultsOnly locks in the L1-defaults /
// L2-overrides contract for object_spec components: shipping `defaults_only`
// is installable, and specdrift maps it to CustomizationPolicyAllow so admin
// OWD/sharingModel changes don't get reported as structural drift.
func TestSourceBackedPackAcceptsObjectSpecDefaultsOnly(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "object:Issue",
				Manageability: ManageabilityDefaultsOnly,
				Payload:       []byte(testObjectSpecPayload("Issue")),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack object_spec defaults_only: %v", err)
	}
	components := pack.Components()
	if got := len(components); got != 1 {
		t.Fatalf("components = %d, want 1", got)
	}
	declared, err := pack.DeclaredComponents()
	if err != nil {
		t.Fatalf("DeclaredComponents error = %v", err)
	}
	if got := len(declared); got != 1 {
		t.Fatalf("DeclaredComponents() = %d, want 1", got)
	}
	if got := components[0].Manageability(); got != ManageabilityDefaultsOnly {
		t.Fatalf("component manageability = %q, want %q", got, ManageabilityDefaultsOnly)
	}
	if got := specdriftPolicyForManageability(ManageabilityDefaultsOnly); got != specdrift.CustomizationPolicyAllow {
		t.Fatalf("specdrift policy for defaults_only = %v, want CustomizationPolicyAllow", got)
	}
}

// TestSourceBackedPackOverlaysChildCloneRulesIntoObjectSpec locks in the #965
// source-of-truth boundary: child_clone_rules is a manifest partition, while
// object_spec remains the DB writer/verifier for ChildCloneRules sidecars.
func TestSourceBackedPackOverlaysChildCloneRulesIntoObjectSpec(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "object:Issue",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(testObjectSpecPayloadWithFields("Issue",
						`{"apiName":"Subject","label":"Subject","type":"string","required":true,"isNameField":true}`,
						`{"apiName":"ParentId","label":"Parent","type":"reference","referenceTo":["Issue"],"createable":true,"updateable":true}`,
					)),
				},
				{
					Kind:          ComponentKindChildCloneRules,
					BusinessKey:   "child-clone-rules",
					Manageability: ManageabilityLocked,
					DependsOn: []ComponentReference{{
						Kind:        ComponentKindObjectSpec,
						BusinessKey: "object:Issue",
					}},
					Payload: []byte(`{"Issue":[{"parentLookupField":"ParentId","sourceObject":"Issue","sourceLookupField":"ParentId","targetObject":"Issue","targetLookupField":"ParentId","fieldMappings":{"Subject":"Subject"}}]}`),
				},
			},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	var objectComponent *sourceBackedObjectSpecComponent
	for _, c := range pack.Components() {
		if candidate, ok := c.(*sourceBackedObjectSpecComponent); ok {
			objectComponent = candidate
			break
		}
	}
	if objectComponent == nil {
		t.Fatal("object_spec component missing")
	}
	if got := len(objectComponent.specs); got != 1 {
		t.Fatalf("object specs = %d, want 1", got)
	}
	if got := len(objectComponent.specs[0].ChildCloneRules); got != 1 {
		t.Fatalf("overlaid child clone rules = %d, want 1", got)
	}
	if err := ValidateChildCloneRulesForSpecs(context.Background(), objectComponent.specs, nil, true); err != nil {
		t.Fatalf("overlaid child clone rules failed validation: %v", err)
	}
	declared, err := pack.DeclaredComponents()
	if err != nil {
		t.Fatalf("DeclaredComponents: %v", err)
	}
	if got := len(declared); got != 2 {
		t.Fatalf("declared components = %d, want 2", got)
	}
	runtimeSpecs, err := ParseObjectSpecsPayloads(pack.RuntimeObjectSpecPayloads(), "runtime-object-spec")
	if err != nil {
		t.Fatalf("ParseObjectSpecsPayloads(RuntimeObjectSpecPayloads): %v", err)
	}
	if got := len(runtimeSpecs); got != 1 {
		t.Fatalf("runtime object specs = %d, want 1", got)
	}
	if got := len(runtimeSpecs[0].ChildCloneRules); got != 1 {
		t.Fatalf("runtime child clone rules = %d, want 1", got)
	}
}

func TestSourceBackedPackRejectsChildCloneRulesWhenObjectSpecAlreadyAuthorsRules(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "object:Issue",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(`[
  {
    "apiName": "Issue",
    "label": "Issue",
    "tableType": "standard_object",
    "category": "jira",
    "sharingModel": "Private",
    "fields": [
      {"apiName":"Subject","label":"Subject","type":"string","required":true,"isNameField":true},
      {"apiName":"ParentId","label":"Parent","type":"reference","referenceTo":["Issue"],"createable":true,"updateable":true}
    ],
    "childCloneRules": [{
      "parentLookupField":"ParentId",
      "sourceObject":"Issue",
      "sourceLookupField":"ParentId",
      "targetObject":"Issue",
      "targetLookupField":"ParentId",
      "fieldMappings":{"Subject":"Subject"}
    }]
  }
]`),
				},
				{
					Kind:          ComponentKindChildCloneRules,
					BusinessKey:   "child-clone-rules",
					Manageability: ManageabilityLocked,
					DependsOn: []ComponentReference{{
						Kind:        ComponentKindObjectSpec,
						BusinessKey: "object:Issue",
					}},
					Payload: []byte(`{"Issue":[{"parentLookupField":"ParentId","sourceObject":"Issue","sourceLookupField":"ParentId","targetObject":"Issue","targetLookupField":"ParentId","fieldMappings":{"Subject":"Subject"}}]}`),
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack accepted child_clone_rules for an object_spec that already authors childCloneRules")
	}
	if !strings.Contains(err.Error(), "object_spec.childCloneRules") || !strings.Contains(err.Error(), "exactly one pack surface") {
		t.Fatalf("error did not surface the one-source-of-truth conflict: %v", err)
	}
}

func TestSourceBackedPackDroppedChildCloneRulesComponentLeavesObjectSpecEmpty(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "object:Issue",
				Manageability: ManageabilityDefaultsOnly,
				Payload:       []byte(testObjectSpecPayload("Issue")),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	var objectComponent *sourceBackedObjectSpecComponent
	for _, c := range pack.Components() {
		if candidate, ok := c.(*sourceBackedObjectSpecComponent); ok {
			objectComponent = candidate
			break
		}
	}
	if objectComponent == nil {
		t.Fatal("object_spec component missing")
	}
	if got := len(objectComponent.specs); got != 1 {
		t.Fatalf("object specs = %d, want 1", got)
	}
	if got := len(objectComponent.specs[0].ChildCloneRules); got != 0 {
		t.Fatalf("child clone rules after dropping component = %d, want 0", got)
	}
}

func TestSourceBackedPackLegacyPayloadHashUsesDeclaredComponents(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "object:Issue",
				Manageability: ManageabilityDefaultsOnly,
				Payload:       []byte(testObjectSpecPayload("Issue")),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}
	if _, ok := Pack(pack).(LegacyPayloadHasher); !ok {
		t.Fatal("SourceBackedPack must implement LegacyPayloadHasher")
	}
	gotLegacy, err := LegacyPackPayloadHash(pack)
	if err != nil {
		t.Fatalf("LegacyPackPayloadHash error = %v", err)
	}
	wantLegacy, err := legacyPackComponentsPayloadHashV0(pack.Components())
	if err != nil {
		t.Fatalf("legacyPackComponentsPayloadHashV0 error = %v", err)
	}
	if gotLegacy != wantLegacy {
		t.Fatalf("LegacyPayloadHash drift: got=%s want=%s", gotLegacy, wantLegacy)
	}
	format, err := ValidateStoredPackPayloadHash(pack, wantLegacy)
	if err != nil {
		t.Fatalf("ValidateStoredPackPayloadHash(legacy) error = %v", err)
	}
	if format != PayloadHashFormatLegacyV0 {
		t.Fatalf("ValidateStoredPackPayloadHash format = %q, want %q", format, PayloadHashFormatLegacyV0)
	}
}

// TestSourceBackedPackRejectsMultipleChildCloneRulesComponents keeps the
// manifest partition unambiguous: object_spec owns the DB sidecars, so #929
// accepts a single child_clone_rules partition per pack until per-object
// component partitioning is intentionally designed.
func TestSourceBackedPackRejectsMultipleChildCloneRulesComponents(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				testObjectSpecManifestComponent("object:Issue", "Issue"),
				testObjectSpecManifestComponent("object:Bug", "Bug"),
				{
					Kind:          ComponentKindChildCloneRules,
					BusinessKey:   "rules:Issue",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"Issue": []}`),
					DependsOn: []ComponentReference{
						{Kind: ComponentKindObjectSpec, BusinessKey: "object:Issue"},
					},
				},
				{
					Kind:          ComponentKindChildCloneRules,
					BusinessKey:   "rules:Bug",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"Bug": []}`),
					DependsOn: []ComponentReference{
						{Kind: ComponentKindObjectSpec, BusinessKey: "object:Bug"},
					},
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack accepted two child_clone_rules components; the object_spec overlay supports exactly one per pack")
	}
	if !strings.Contains(err.Error(), "more than one child_clone_rules") {
		t.Fatalf("error did not surface the duplicate-component reason: %v", err)
	}
}

func TestSourceBackedPackRejectsChildCloneRulesCanonicalObjectCollision(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				testObjectSpecManifestComponent("object:Issue", "Issue"),
				{
					Kind:          ComponentKindChildCloneRules,
					BusinessKey:   "rules:Issue",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"Issue": [], " issue ": []}`),
					DependsOn: []ComponentReference{
						{Kind: ComponentKindObjectSpec, BusinessKey: "object:Issue"},
					},
				},
			},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack accepted canonical duplicate child_clone_rules object keys")
	}
	if !strings.Contains(err.Error(), "duplicate objectApiName") {
		t.Fatalf("error did not surface the duplicate object key reason: %v", err)
	}
}

// TestValidateManifestComponentsRejectsDuplicateChildCloneRules covers the
// operator-facing surface (pack lint / setup install-pack) - duplicate
// child_clone_rules components must produce a clean diagnostic before the
// pack constructor ever runs. Sibling to the in-binary fail-safe verified
// by TestSourceBackedPackRejectsMultipleChildCloneRulesComponents.
func TestValidateManifestComponentsRejectsDuplicateChildCloneRules(t *testing.T) {
	components := []ManifestComponent{
		testObjectSpecManifestComponent("object:Issue", "Issue"),
		testObjectSpecManifestComponent("object:Bug", "Bug"),
		{
			Kind:          ComponentKindChildCloneRules,
			BusinessKey:   "rules:Issue",
			Manageability: ManageabilityLocked,
			Payload:       []byte(`{"Issue": []}`),
			DependsOn: []ComponentReference{
				{Kind: ComponentKindObjectSpec, BusinessKey: "object:Issue"},
			},
		},
		{
			Kind:          ComponentKindChildCloneRules,
			BusinessKey:   "rules:Bug",
			Manageability: ManageabilityLocked,
			Payload:       []byte(`{"Bug": []}`),
			DependsOn: []ComponentReference{
				{Kind: ComponentKindObjectSpec, BusinessKey: "object:Bug"},
			},
		},
	}
	diagnostics := validateManifestComponents(components, DefaultSourceBackedComponentKindRegistry())
	foundDuplicate := false
	for _, d := range diagnostics {
		if d.Code == "component.child_clone_rules.duplicate" {
			foundDuplicate = true
			break
		}
	}
	if !foundDuplicate {
		t.Fatalf("validateManifestComponents did not flag duplicate child_clone_rules; diagnostics = %+v", diagnostics)
	}
}

func TestVerifyTableTypesForObjectSpecsIncludesCustomObjects(t *testing.T) {
	got := verifyTableTypesForObjectSpecs([]ObjectSpec{
		{APIName: "Account", TableType: string(constants.TableTypeStandardObject)},
		{APIName: "PingCAP_Config__c", TableType: string(constants.TableTypeCustomObject)},
	})
	want := []string{string(constants.TableTypeStandardObject), string(constants.TableTypeCustomObject)}
	if len(got) != len(want) {
		t.Fatalf("tableTypes len = %d, want %d (%v)", len(got), len(want), got)
	}
	for i := range want {
		if string(got[i]) != want[i] {
			t.Fatalf("tableTypes[%d] = %q, want %q", i, got[i], want[i])
		}
	}
}

func TestFrozenFormulaSchemasFromObjectSpecsInjectRecordTypeIDOnlyForCustomObjects(t *testing.T) {
	schemas := frozenFormulaSchemasFromObjectSpecs([]ObjectSpec{
		{
			APIName:      "Account",
			Label:        "Account",
			TableType:    string(constants.TableTypeStandardObject),
			Category:     "crm",
			SharingModel: "Private",
			Fields: []FieldSpec{{
				APIName:     "Name",
				Label:       "Name",
				Type:        "string",
				Required:    true,
				IsNameField: true,
			}},
		},
		{
			APIName:      "Project__c",
			Label:        "Project",
			TableType:    string(constants.TableTypeCustomObject),
			Category:     "tenant",
			SharingModel: "Private",
			Fields: []FieldSpec{{
				APIName:     "Name",
				Label:       "Name",
				Type:        "string",
				Required:    true,
				IsNameField: true,
			}},
		},
	})

	account := schemas["Account"]
	if account == nil {
		t.Fatal("Account schema missing")
	}
	project := schemas["Project__c"]
	if project == nil {
		t.Fatal("Project__c schema missing")
	}

	if frozenFormulaSchemaHasField(account, constants.FieldRecordTypeID) {
		t.Fatalf("standard-object frozen schema unexpectedly includes %s", constants.FieldRecordTypeID)
	}
	if !frozenFormulaSchemaHasField(project, constants.FieldRecordTypeID) {
		t.Fatalf("custom-object frozen schema missing %s", constants.FieldRecordTypeID)
	}
}

func TestSourceBackedCRMPackConstructsAllManifestComponents(t *testing.T) {
	registerCRMHostPrimitivesForTest(t)
	opts := officialCRMPackSourceOptionsForTest(t)

	source, diagnostics := ResolvePackSourceWithOptions("official_repo:salesforce-crm-standard", opts)
	if len(diagnostics) > 0 {
		t.Fatalf("resolve CRM source diagnostics: %v", diagnostics)
	}
	pack, err := NewSourceBackedPack(source, officialCRMSourceBackedPackOptionsForTest(t, opts)...)
	if err != nil {
		t.Fatalf("construct CRM source-backed pack: %v", err)
	}
	if pack.Category() != AppPack {
		t.Fatalf("CRM pack category = %q, want %q", pack.Category(), AppPack)
	}
	// Independent manifest oracle — this test asserts the constructor processes
	// every component, so the oracle MUST NOT route through NewSourceBackedPack
	// (the SUT). Read pack.json directly and count its components array.
	var manifest struct {
		Components []struct {
			Kind string `json:"kind"`
		} `json:"components"`
	}
	manifestRaw, err := os.ReadFile(filepath.Join(officialPackRootFromThisFile(t), "salesforce-crm-standard", "pack.json"))
	if err != nil {
		t.Fatalf("read pack.json: %v", err)
	}
	if err := json.Unmarshal(manifestRaw, &manifest); err != nil {
		t.Fatalf("parse pack.json: %v", err)
	}
	if got, want := len(pack.Components()), len(manifest.Components); got != want {
		t.Fatalf("CRM pack components = %d, want %d (must match manifest)", got, want)
	}
}

func TestResolveInstalledSourceBackedPackAllowsLegacyActiveScalarFormula(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-active-formula-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "SubjectEcho", "label": "Subject Echo", "type": "string", "formulaMode": "active", "formula": "Subject", "returnType": "string"}`,
	)+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted manifest v1 legacy active-scalar formula shape; want fail-closed error")
	}
	assertInstallableDiagnostic(t, report, "field.formula.expression_on_non_formula_type")

	pack, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, SupportedManifestFormatVersion)
	if err != nil {
		t.Fatalf("resolveInstalledSourceBackedPackWithStoredManifestVersion legacy active-scalar v1 row: %v", err)
	}
	if pack == nil {
		t.Fatal("ResolveInstalledSourceBackedPack returned nil pack")
	}
	if got := pack.Name(); got != "jira-like-sample" {
		t.Fatalf("resolved pack name = %q, want jira-like-sample", got)
	}
}

func TestResolveInstalledSourceBackedPackLegacyCompatRequiresStoredManifestV1(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-active-formula-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "SubjectEcho", "label": "Subject Echo", "type": "string", "formulaMode": "active", "formula": "Subject", "returnType": "string"}`,
	)+`
    }
  ]`, `"dependencies": [],`))

	if _, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, ""); err == nil {
		t.Fatal("resolveInstalledSourceBackedPackWithStoredManifestVersion accepted legacy active-scalar source without stored manifest version; want strict rejection")
	}
	if _, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, ManifestFormatVersion2); err == nil {
		t.Fatal("resolveInstalledSourceBackedPackWithStoredManifestVersion accepted legacy active-scalar source for stored manifest v2; want strict rejection")
	}
	if _, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, SupportedManifestFormatVersion); err != nil {
		t.Fatalf("resolveInstalledSourceBackedPackWithStoredManifestVersion legacy active-scalar v1 row: %v", err)
	}
}

func TestResolveInstalledPackAllowsStoredManifestV1CompatDespiteFreshSourceRejection(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-active-formula-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "issue",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Issue",
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "SubjectEcho", "label": "Subject Echo", "type": "string", "formulaMode": "active", "formula": "Subject", "returnType": "string"}`,
	)+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted manifest v1 legacy active-scalar source; want fail-closed error")
	}
	assertInstallableDiagnostic(t, report, "field.formula.expression_on_non_formula_type")

	pack, err := ResolveInstalledPack(
		InstalledPackResolverInput{
			Name:                  "jira-like-sample",
			Category:              AppPack,
			ManifestFormatVersion: SupportedManifestFormatVersion,
			SourceKind:            string(PackSourceKindLocalPath),
			SourceURI:             root,
		},
		InstalledPackResolverOptions{AllowBundledFallback: false},
	)
	if err != nil {
		t.Fatalf("ResolveInstalledPack manifest v1 legacy source: %v", err)
	}
	if _, ok := pack.(*SourceBackedPack); !ok {
		t.Fatalf("ResolveInstalledPack type = %T, want *SourceBackedPack", pack)
	}

	if _, err := ResolveInstalledPack(
		InstalledPackResolverInput{
			Name:                  "jira-like-sample",
			Category:              AppPack,
			ManifestFormatVersion: ManifestFormatVersion2,
			SourceKind:            string(PackSourceKindLocalPath),
			SourceURI:             root,
		},
		InstalledPackResolverOptions{AllowBundledFallback: false},
	); err == nil {
		t.Fatal("ResolveInstalledPack accepted legacy active-scalar source for installed-row manifest v2; want strict rejection")
	}
}

func TestResolveInstalledPackAllowsLegacyFolderTypeMissingCreatePermission(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-folder-type-pack", validPackManifest(`[
    {
      "kind": "folder_type",
      "businessKey": "folder-types",
      "manageability": "locked",
      "payload": {
        "folderTypes": [
          {
            "id": "folder_type_work_items",
            "apiName": "WorkItem",
            "label": "Work Items",
            "filterGroup": "Work",
            "managePermission": "ManageWorkItems",
            "personalFolderName": "My Work Items",
            "personalFolderDeveloperNamePrefix": "my_work_items"
          }
        ]
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted folder_type without createPermission; want fresh authoring rejection")
	}
	if !strings.Contains(report.Error(), "createPermission is required") {
		t.Fatalf("ValidateInstallablePackSourceWithOptions error = %q, want createPermission rejection", report.Error())
	}

	pack, err := ResolveInstalledPack(
		InstalledPackResolverInput{
			Name:                  "jira-like-sample",
			Category:              AppPack,
			ManifestFormatVersion: SupportedManifestFormatVersion,
			SourceKind:            string(PackSourceKindLocalPath),
			SourceURI:             root,
		},
		InstalledPackResolverOptions{AllowBundledFallback: false},
	)
	if err != nil {
		t.Fatalf("ResolveInstalledPack legacy folder_type source: %v", err)
	}
	if _, ok := pack.(*SourceBackedPack); !ok {
		t.Fatalf("ResolveInstalledPack type = %T, want *SourceBackedPack", pack)
	}
}

func TestResolveInstalledPackRejectsLegacyFolderTypeMissingRuntimeMetadata(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-folder-type-runtime-pack", validPackManifest(`[
    {
      "kind": "folder_type",
      "businessKey": "folder-types",
      "manageability": "locked",
      "payload": {
        "folderTypes": [
          {
            "id": "folder_type_work_items",
            "apiName": "WorkItem",
            "label": "Work Items",
            "createPermission": "CreateWorkItems",
            "managePermission": "ManageWorkItems"
          }
        ]
      }
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted folder_type without runtime metadata; want fresh authoring rejection")
	}
	if !strings.Contains(report.Error(), "filterGroup is required") {
		t.Fatalf("ValidateInstallablePackSourceWithOptions error = %q, want filterGroup rejection", report.Error())
	}

	_, err := ResolveInstalledPack(
		InstalledPackResolverInput{
			Name:                  "jira-like-sample",
			Category:              AppPack,
			ManifestFormatVersion: SupportedManifestFormatVersion,
			SourceKind:            string(PackSourceKindLocalPath),
			SourceURI:             root,
		},
		InstalledPackResolverOptions{AllowBundledFallback: false},
	)
	if err == nil {
		t.Fatal("ResolveInstalledPack accepted legacy folder_type runtime metadata source; want strict rejection")
	}
	if !strings.Contains(err.Error(), "filterGroup is required") {
		t.Fatalf("ResolveInstalledPack error = %q, want filterGroup rejection", err.Error())
	}
}

func TestResolveInstalledSourceBackedPackAllowsLegacyAggregateFormulaCompat(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-aggregate-formula-pack", validPackManifest(`[
    {
      "kind": "object_spec",
      "businessKey": "parent",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Parent__c",
		`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "Total__c", "label": "Total", "type": "formula", "formulaMode": "active", "formula": "SUM((Child__c.Amount__c))", "returnType": "double"}`,
	)+`
    }
  ]`, `"dependencies": [],`))

	report := ValidateInstallablePackSourceWithOptions("local_path:"+root, testPackSourceOptions(t))
	if !report.HasErrors() {
		t.Fatal("ValidateInstallablePackSourceWithOptions accepted aggregate formula source; want fail-closed error")
	}
	assertInstallableDiagnostic(t, report, "field.formula.aggregate_authoring_requires_rollupsummary")

	pack, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, SupportedManifestFormatVersion)
	if err != nil {
		t.Fatalf("resolveInstalledSourceBackedPackWithStoredManifestVersion legacy aggregate v1 row: %v", err)
	}
	if pack == nil {
		t.Fatal("ResolveInstalledSourceBackedPack returned nil pack")
	}
	objectComponent, ok := pack.Components()[0].(*sourceBackedObjectSpecComponent)
	if !ok {
		t.Fatalf("first component type = %T, want *sourceBackedObjectSpecComponent", pack.Components()[0])
	}
	if !objectComponent.fieldCompat.allowLegacyAggregateFormulaAuthoring {
		t.Fatal("legacy installed aggregate pack did not carry apply-side aggregate compat")
	}
	sm := metadata.NewSchemaManager(persistence.NewSchemaRepository(nil, nil))
	if _, err := buildFieldMetadataWithResolver(objectComponent.packName, objectComponent.specs, sm, false, objectComponent.fieldResolver(), objectComponent.fieldCompat); err != nil {
		t.Fatalf("buildFieldMetadataWithResolver legacy aggregate compat: %v", err)
	}
}

func TestResolveInstalledSourceBackedPackRejectsLegacyAggregateCompatForManifestV2Source(t *testing.T) {
	root := writePackFixture(t, "legacy-installed-aggregate-formula-pack-v2", validPackManifestV2(`[
    {
      "kind": "object_spec",
      "businessKey": "parent",
      "manageability": "locked",
      "payload": `+testObjectSpecPayloadWithFields("Parent__c",
		`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
		`{"apiName": "Total__c", "label": "Total", "type": "formula", "formulaMode": "active", "formula": "SUM((Child__c.Amount__c))", "returnType": "double"}`,
	)+`
    }
  ]`, `"dependencies": [],`))

	if _, err := resolveInstalledSourceBackedPackWithStoredManifestVersion("jira-like-sample", AppPack, PackSourceKindLocalPath, root, SupportedManifestFormatVersion); err == nil {
		t.Fatal("resolveInstalledSourceBackedPackWithStoredManifestVersion accepted aggregate formula source for manifest v2; want strict rejection")
	} else if !strings.Contains(err.Error(), "field.formula.aggregate_authoring_requires_rollupsummary") {
		t.Fatalf("resolveInstalledSourceBackedPackWithStoredManifestVersion error = %q, want aggregate authoring diagnostic", err.Error())
	}
}

func TestSourceBackedCRMPackTaskIsClosedUsesLifecycleDerivedResolver(t *testing.T) {
	registerCRMHostPrimitivesForTest(t)
	opts := officialCRMPackSourceOptionsForTest(t)

	source, diagnostics := ResolvePackSourceWithOptions("official_repo:salesforce-crm-standard", opts)
	if len(diagnostics) > 0 {
		t.Fatalf("resolve CRM source diagnostics: %v", diagnostics)
	}
	pack, err := NewSourceBackedPack(source, officialCRMSourceBackedPackOptionsForTest(t, opts)...)
	if err != nil {
		t.Fatalf("construct CRM source-backed pack: %v", err)
	}

	if !slices.Contains(pack.ObjectsRequiringDerivedViews(), "Task") {
		t.Fatalf("ObjectsRequiringDerivedViews() = %v, want Task", pack.ObjectsRequiringDerivedViews())
	}

	var taskSpec *ObjectSpec
	for _, component := range pack.Components() {
		objectComponent, ok := component.(*sourceBackedObjectSpecComponent)
		if !ok {
			continue
		}
		for i := range objectComponent.specs {
			if objectComponent.specs[i].APIName == "Task" {
				taskSpec = &objectComponent.specs[i]
				break
			}
		}
		if taskSpec != nil {
			break
		}
	}
	if taskSpec == nil {
		t.Fatal("Task object spec missing from source-backed CRM pack")
	}

	isClosed := findFieldSpec(t, *taskSpec, "IsClosed")
	if got := isClosed.FormulaMode; got != "active" {
		t.Fatalf("Task.IsClosed formulaMode = %q, want active", got)
	}
	if got := strings.TrimSpace(isClosed.Formula); got == "" {
		t.Fatal("Task.IsClosed formula is blank, want lifecycle-derived resolver")
	}
	if !strings.Contains(isClosed.Formula, "Status") {
		t.Fatalf("Task.IsClosed formula = %q, want Status-based resolver", isClosed.Formula)
	}

	var effectivePayload []byte
	for _, component := range pack.Components() {
		if component.Kind() != ComponentKindObjectSpec {
			continue
		}
		payload, payloadErr := component.ExpectedPayload()
		if payloadErr != nil {
			t.Fatalf("ExpectedPayload(%s): %v", component.BusinessKey(), payloadErr)
		}
		specs, parseErr := parseObjectSpecsJSON(payload, "effective payload")
		if parseErr != nil {
			t.Fatalf("parse object spec effective payload: %v", parseErr)
		}
		for _, spec := range specs {
			if spec.APIName != "Task" {
				continue
			}
			effectivePayload = payload
			break
		}
		if len(effectivePayload) > 0 {
			break
		}
	}
	if len(effectivePayload) == 0 {
		t.Fatal("object spec component effective payload missing")
	}

	effectiveSpecs, err := parseObjectSpecsJSON(effectivePayload, "effective payload")
	if err != nil {
		t.Fatalf("parse object spec effective payload: %v", err)
	}
	var effectiveTaskSpec *ObjectSpec
	for i := range effectiveSpecs {
		if effectiveSpecs[i].APIName == "Task" {
			effectiveTaskSpec = &effectiveSpecs[i]
			break
		}
	}
	if effectiveTaskSpec == nil {
		t.Fatal("Task object spec missing from effective object spec payload")
	}
	effectiveIsClosed := findFieldSpec(t, *effectiveTaskSpec, "IsClosed")
	if got := effectiveIsClosed.FormulaMode; got != "active" {
		t.Fatalf("effective Task.IsClosed formulaMode = %q, want active", got)
	}
	if !strings.Contains(effectiveIsClosed.Formula, "Status") {
		t.Fatalf("effective Task.IsClosed formula = %q, want Status-based resolver", effectiveIsClosed.Formula)
	}
}

func TestSourceBackedCRMPackHashIsSourceLocationNeutral(t *testing.T) {
	registerCRMHostPrimitivesForTest(t)
	opts := officialCRMPackSourceOptionsForTest(t)

	officialRepoSource, diagnostics := ResolvePackSourceWithOptions("official_repo:salesforce-crm-standard", opts)
	if len(diagnostics) > 0 {
		t.Fatalf("resolve official CRM source diagnostics: %v", diagnostics)
	}
	officialRepoPack, err := NewSourceBackedPack(officialRepoSource, officialCRMSourceBackedPackOptionsForTest(t, opts)...)
	if err != nil {
		t.Fatalf("construct official CRM source-backed pack: %v", err)
	}

	externalRoot := filepath.Join(t.TempDir(), "salesforce-crm-standard")
	copyTestDirectory(t, filepath.Join(officialPackRootFromThisFile(t), "salesforce-crm-standard"), externalRoot)
	localSource, diagnostics := ResolvePackSourceWithOptions("local_path:"+externalRoot, opts)
	if len(diagnostics) > 0 {
		t.Fatalf("resolve external CRM source diagnostics: %v", diagnostics)
	}
	localPack, err := NewSourceBackedPack(localSource, officialCRMSourceBackedPackOptionsForTest(t, opts)...)
	if err != nil {
		t.Fatalf("construct external CRM source-backed pack: %v", err)
	}

	officialRepoHash, err := officialRepoPack.PayloadHash()
	if err != nil {
		t.Fatalf("official_repo CRM hash: %v", err)
	}
	localHash, err := localPack.PayloadHash()
	if err != nil {
		t.Fatalf("external CRM hash: %v", err)
	}
	if officialRepoHash != localHash {
		t.Fatalf("same CRM payload produced location-dependent hashes:\nofficial_repo=%s\nlocal_path=%s", officialRepoHash, localHash)
	}

	localURI, err := filepath.EvalSymlinks(externalRoot)
	if err != nil {
		t.Fatalf("eval external root: %v", err)
	}
	localMeta := localPack.PackHashMetadata()
	if !localMeta.SourceURIValid || localMeta.SourceURI != localURI {
		t.Fatalf("external CRM SourceURI = %#v, want canonical %q", localMeta, localURI)
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityAllowsNonPackOwnedFieldResidue(t *testing.T) {
	tests := []struct {
		name string
		// residueRow is the field-registry row for the admin__c residue field:
		// either an explicit admin owner or an unowned legacy-custom leftover.
		residueRow packFieldRegistryRow
	}{
		{name: "AdminFieldOverlay", residueRow: packFieldRegistryRow{ownerKind: "admin"}},
		{name: "LegacyCustomFieldResidue", residueRow: packFieldRegistryRow{}},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			def := domainschema.TableDefinition{
				TableName: "Issue",
				TableType: "standard_object",
				Category:  "jira",
				Columns: []domainschema.ColumnDefinition{
					{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true},
					{Name: "Subject", Type: "VARCHAR(255)"},
				},
			}
			err := verifyOwnerlessObjectSpecPartialCompatibility(
				nil,
				nil,
				"jira-like-sample",
				def,
				map[string]bool{"issue": true},
				nil,
				map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
				map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
				map[string]map[string]packFieldRegistryRow{
					"issue": {
						"id":       {ownerKind: "pack", ownerPack: "jira-like-sample"},
						"subject":  {ownerKind: "pack", ownerPack: "jira-like-sample"},
						"admin__c": tc.residueRow,
					},
				},
			)
			if err != nil {
				t.Fatalf("verifyOwnerlessObjectSpecPartialCompatibility non-pack-owned residue: %v", err)
			}
		})
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityAllowsExistingVarcharWidthWithoutPhysicalRewrite(t *testing.T) {
	fieldConstraints := newObjectSpecRequiredFieldIndex([]ObjectSpec{{
		APIName: "Contact",
		Fields: []FieldSpec{{
			APIName: "Salesforce_Identity_Key__c",
			Type:    "string",
			Length:  80,
		}},
	}})
	def := domainschema.TableDefinition{
		TableName: "Contact",
		TableType: "standard_object",
		Category:  "crm",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true, Nullable: false},
			{Name: "Salesforce_Identity_Key__c", Type: "VARCHAR(255)", Nullable: true},
		},
	}
	err := verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"salesforce-crm-standard",
		def,
		map[string]bool{"contact": true},
		map[string]map[string]schemashape.PhysicalColumnShape{
			"contact": {
				"id":                         {ColumnType: "varchar(255)", Nullable: false},
				"salesforce_identity_key__c": {ColumnType: "varchar(80)", Nullable: true},
			},
		},
		map[string]packTableRegistryRow{"contact": {tableType: "standard_object", category: "crm", managed: true}},
		map[string]packObjectRegistryRow{"contact": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"contact": {
				"id":                         {ownerKind: "pack", ownerPack: "salesforce-crm-standard"},
				"salesforce_identity_key__c": {ownerKind: "pack", ownerPack: "salesforce-crm-standard"},
			},
		},
		fieldConstraints,
	)
	if err != nil {
		t.Fatalf("verifyOwnerlessObjectSpecPartialCompatibility existing varchar width: %v", err)
	}

	physicalColumns := map[string]map[string]schemashape.PhysicalColumnShape{
		"contact": {
			"id":                         {ColumnType: "varchar(255)", Nullable: false},
			"salesforce_identity_key__c": {ColumnType: "varchar(79)", Nullable: true},
		},
	}
	err = verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"salesforce-crm-standard",
		def,
		map[string]bool{"contact": true},
		physicalColumns,
		map[string]packTableRegistryRow{"contact": {tableType: "standard_object", category: "crm", managed: true}},
		map[string]packObjectRegistryRow{"contact": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"contact": {
				"id":                         {ownerKind: "pack", ownerPack: "salesforce-crm-standard"},
				"salesforce_identity_key__c": {ownerKind: "pack", ownerPack: "salesforce-crm-standard"},
			},
		},
		fieldConstraints,
	)
	if err == nil {
		t.Fatal("verifyOwnerlessObjectSpecPartialCompatibility accepted varchar below Catalog MaxLength")
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityAllowsRequiredFieldOnExistingNotNullPhysicalColumn(t *testing.T) {
	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true, Nullable: false},
			{Name: "Subject", Type: "VARCHAR(255)", Nullable: true},
		},
	}
	requiredFields := newObjectSpecRequiredFieldIndex([]ObjectSpec{{
		APIName: "Issue",
		Fields: []FieldSpec{{
			APIName:  "Subject",
			Type:     "string",
			Required: true,
		}},
	}})
	err := verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		map[string]map[string]schemashape.PhysicalColumnShape{
			"issue": {
				"id":      {ColumnType: "varchar(255)", Nullable: false},
				"subject": {ColumnType: "varchar(255)", Nullable: false},
			},
		},
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":      {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"subject": {ownerKind: "pack", ownerPack: "jira-like-sample"},
			},
		},
		requiredFields,
	)
	if err != nil {
		t.Fatalf("verifyOwnerlessObjectSpecPartialCompatibility required not-null retry: %v", err)
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityRejectsOptionalFieldOnExistingNotNullPhysicalColumn(t *testing.T) {
	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true, Nullable: false},
			{Name: "Subject", Type: "VARCHAR(255)", Nullable: true},
		},
	}
	err := verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		map[string]map[string]schemashape.PhysicalColumnShape{
			"issue": {
				"id":      {ColumnType: "varchar(255)", Nullable: false},
				"subject": {ColumnType: "varchar(255)", Nullable: false},
			},
		},
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":      {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"subject": {ownerKind: "pack", ownerPack: "jira-like-sample"},
			},
		},
	)
	if err == nil {
		t.Fatal("verifyOwnerlessObjectSpecPartialCompatibility = nil, want optional not-null drift")
	}
	if !strings.Contains(err.Error(), "physical column Issue.Subject shape") {
		t.Fatalf("error = %v, want physical shape drift", err)
	}
}

func TestPackObjectSpecColumnShapeRetryCompatibleAllowsOnlyDefaultDrift(t *testing.T) {
	tests := []struct {
		name    string
		actual  schemashape.PhysicalColumnShape
		desired domainschema.ColumnDefinition
		want    bool
	}{
		{
			name: "replace string default",
			actual: schemashape.PhysicalColumnShape{
				ColumnType: "varchar(255)",
				Nullable:   true,
				DefaultVal: sql.NullString{String: "Not Started", Valid: true},
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "'Open'"},
			want:    true,
		},
		{
			name: "reverse string default",
			actual: schemashape.PhysicalColumnShape{
				ColumnType: "varchar(255)",
				Nullable:   true,
				DefaultVal: sql.NullString{String: "Open", Valid: true},
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "'Not Started'"},
			want:    true,
		},
		{
			name: "add empty string default",
			actual: schemashape.PhysicalColumnShape{
				ColumnType: "varchar(255)",
				Nullable:   true,
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "''"},
			want:    true,
		},
		{
			name: "reject simultaneous type drift",
			actual: schemashape.PhysicalColumnShape{
				ColumnType: "varchar(128)",
				Nullable:   true,
				DefaultVal: sql.NullString{String: "Not Started", Valid: true},
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "'Open'"},
			want:    false,
		},
		{
			name: "reject simultaneous nullability drift",
			actual: schemashape.PhysicalColumnShape{
				ColumnType: "varchar(255)",
				Nullable:   false,
				DefaultVal: sql.NullString{String: "Not Started", Valid: true},
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "'Open'"},
			want:    false,
		},
		{
			name: "reject simultaneous authored collation drift",
			actual: schemashape.PhysicalColumnShape{
				ColumnType:    "varchar(255)",
				Nullable:      true,
				DefaultVal:    sql.NullString{String: "Not Started", Valid: true},
				CollationName: sql.NullString{String: "utf8mb4_unicode_ci", Valid: true},
			},
			desired: domainschema.ColumnDefinition{Name: "Status", Type: "VARCHAR(255)", Nullable: true, Default: "'Open'", CollationName: "utf8mb4_bin"},
			want:    false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := packObjectSpecColumnShapeRetryCompatible(context.Background(), nil, "Task", tt.actual, tt.desired, nil)
			if err != nil {
				t.Fatalf("packObjectSpecColumnShapeRetryCompatible: %v", err)
			}
			if got != tt.want {
				t.Fatalf("packObjectSpecColumnShapeRetryCompatible = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityAllowsNullablePhysicalColumnWhenNotNullTightenIsDataSafe(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*) FROM `Issue` WHERE `ParentId` IS NULL")).
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(0))

	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true, Nullable: false},
			{Name: "ParentId", Type: "VARCHAR(255)", Nullable: false},
		},
	}
	err = verifyOwnerlessObjectSpecPartialCompatibility(
		context.Background(),
		db,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		map[string]map[string]schemashape.PhysicalColumnShape{
			"issue": {
				"id":       {ColumnType: "varchar(255)", Nullable: false},
				"parentid": {ColumnType: "varchar(255)", Nullable: true},
			},
		},
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":       {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"parentid": {ownerKind: "pack", ownerPack: "jira-like-sample"},
			},
		},
	)
	if err != nil {
		t.Fatalf("verifyOwnerlessObjectSpecPartialCompatibility nullable physical with zero nulls: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityRejectsNullablePhysicalColumnWhenNotNullTightenHasNullRows(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta("SELECT COUNT(*) FROM `Issue` WHERE `ParentId` IS NULL")).
		WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(2))

	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true, Nullable: false},
			{Name: "ParentId", Type: "VARCHAR(255)", Nullable: false},
		},
	}
	err = verifyOwnerlessObjectSpecPartialCompatibility(
		context.Background(),
		db,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		map[string]map[string]schemashape.PhysicalColumnShape{
			"issue": {
				"id":       {ColumnType: "varchar(255)", Nullable: false},
				"parentid": {ColumnType: "varchar(255)", Nullable: true},
			},
		},
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":       {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"parentid": {ownerKind: "pack", ownerPack: "jira-like-sample"},
			},
		},
	)
	if err == nil {
		t.Fatal("verifyOwnerlessObjectSpecPartialCompatibility = nil, want data cleanup error")
	}
	if !strings.Contains(err.Error(), "has 2 NULL row(s)") {
		t.Fatalf("error = %v, want NULL row count", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityRejectsUnownedFieldResidue(t *testing.T) {
	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true},
			{Name: "Subject", Type: "VARCHAR(255)"},
		},
	}
	err := verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		nil,
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":      {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"subject": {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"stale":   {},
			},
		},
	)
	if err == nil {
		t.Fatal("verifyOwnerlessObjectSpecPartialCompatibility = nil, want unowned field residue error")
	}
	if !strings.Contains(err.Error(), "not declared by the pack") {
		t.Fatalf("error = %v, want unowned field residue", err)
	}
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityRejectsStandardShapedFieldWithoutOwner(t *testing.T) {
	def := domainschema.TableDefinition{
		TableName: "Issue",
		TableType: "standard_object",
		Category:  "jira",
		Columns: []domainschema.ColumnDefinition{
			{Name: "Id", Type: "VARCHAR(255)", PrimaryKey: true},
			{Name: "Subject", Type: "VARCHAR(255)"},
		},
	}
	err := verifyOwnerlessObjectSpecPartialCompatibility(
		nil,
		nil,
		"jira-like-sample",
		def,
		map[string]bool{"issue": true},
		nil,
		map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}},
		map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}},
		map[string]map[string]packFieldRegistryRow{
			"issue": {
				"id":        {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"subject":   {ownerKind: "pack", ownerPack: "jira-like-sample"},
				"AdminFlag": {},
			},
		},
	)
	if err == nil {
		t.Fatal("verifyOwnerlessObjectSpecPartialCompatibility = nil, want ownerless standard-shaped field rejection")
	}
	if !strings.Contains(err.Error(), "not declared by the pack") {
		t.Fatalf("error = %v, want owner-based rejection", err)
	}
}

func testSourceBackedPack(t *testing.T, dependencies []PackDependency, payloads map[string]string) *SourceBackedPack {
	t.Helper()
	if payloads == nil {
		payloads = map[string]string{"issue": testObjectSpecPayload("Issue")}
	}
	components := make([]ManifestComponent, 0, len(payloads))
	for key, payload := range payloads {
		components = append(components, ManifestComponent{
			Kind:          ComponentKindObjectSpec,
			BusinessKey:   key,
			Manageability: ManageabilityLocked,
			Payload:       []byte(payload),
		})
	}
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/source-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "jira-like-sample",
			DisplayName:           "Jira Like Sample",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Dependencies:          dependencies,
			Components:            components,
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("construct source-backed pack: %v", err)
	}
	return pack
}

func testObjectSpecManifestComponent(key, objectName string) ManifestComponent {
	return ManifestComponent{
		Kind:          ComponentKindObjectSpec,
		BusinessKey:   key,
		Manageability: ManageabilityLocked,
		Payload:       []byte(testObjectSpecPayload(objectName)),
	}
}

func testObjectSpecPayload(objectName string) string {
	return testObjectSpecPayloadWithFields(objectName,
		`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
	)
}

func testObjectSpecPayloadWithFields(objectName string, fieldSpecs ...string) string {
	if len(fieldSpecs) == 0 {
		fieldSpecs = []string{`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`}
	}
	return `[
  {
    "apiName": "` + objectName + `",
    "label": "` + objectName + `",
    "tableType": "standard_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "` + objectName + ` work item",
    "fields": [
      ` + strings.Join(fieldSpecs, ",\n      ") + `
    ]
  }
	]`
}

func testObjectSpecPayloadWithListFields(objectName string, listFields []string, fieldSpecs ...string) string {
	if len(fieldSpecs) == 0 {
		fieldSpecs = []string{`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`}
	}
	rawListFields, _ := json.Marshal(listFields)
	return `[
  {
    "apiName": "` + objectName + `",
    "label": "` + objectName + `",
    "tableType": "standard_object",
    "category": "jira",
    "sharingModel": "Private",
    "enableHierarchySharing": false,
    "description": "` + objectName + ` work item",
    "listFields": ` + string(rawListFields) + `,
    "fields": [
      ` + strings.Join(fieldSpecs, ",\n      ") + `
    ]
  }
]`
}

func copyTestDirectory(t *testing.T, src, dst string) {
	t.Helper()
	if err := filepath.WalkDir(src, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		rel, err := filepath.Rel(src, path)
		if err != nil {
			return err
		}
		target := filepath.Join(dst, rel)
		if d.IsDir() {
			return os.MkdirAll(target, 0o755)
		}
		data, err := os.ReadFile(path)
		if err != nil {
			return err
		}
		return os.WriteFile(target, data, 0o644)
	}); err != nil {
		t.Fatalf("copy %s to %s: %v", src, dst, err)
	}
}

func TestFrozenFormulaRefsFromObjectSpecComponentIncludesPayloadContext(t *testing.T) {
	pack := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "SubjectEcho", "label": "Subject Echo", "type": "string", "formulaMode": "frozen", "formula": "TEXT(Subject)", "returnType": "string"}`,
		),
	})
	component := pack.components[0].(*sourceBackedObjectSpecComponent)

	refs, err := frozenFormulaRefsFromObjectSpecComponent(component)
	if err != nil {
		t.Fatalf("frozenFormulaRefsFromObjectSpecComponent: %v", err)
	}
	if len(refs) != 1 {
		t.Fatalf("refs len = %d, want 1", len(refs))
	}
	if refs[0].Authority != formula.FrozenFormulaAuthorityPackPayload {
		t.Fatalf("authority = %q, want %q", refs[0].Authority, formula.FrozenFormulaAuthorityPackPayload)
	}
	if refs[0].BusinessKey != "issue" || refs[0].ObjectAPIName != "Issue" || refs[0].FieldAPIName != "SubjectEcho" {
		t.Fatalf("unexpected ref = %#v", refs[0])
	}
	if refs[0].Schema == nil {
		t.Fatal("ref Schema = nil, want object-spec schema for schema/runtime parity validation")
	}
}

func TestFrozenFormulaRefsFromObjectSpecsIncludesPayloadContext(t *testing.T) {
	specs := []ObjectSpec{{
		APIName: "Issue",
		Fields: []FieldSpec{
			{APIName: "Subject", Label: "Subject", Type: "string"},
			{APIName: "SubjectEcho", Label: "Subject Echo", Type: "string", FormulaMode: "frozen", Formula: "TEXT(Subject)", ReturnType: "string"},
		},
	}}

	refs := frozenFormulaRefsFromObjectSpecs("extension-pack", string(ComponentKindCustomField), "Issue.SubjectEcho", string(ManageabilityLocked), specs, nil)
	if len(refs) != 1 {
		t.Fatalf("refs len = %d, want 1", len(refs))
	}
	if refs[0].Authority != formula.FrozenFormulaAuthorityPackPayload {
		t.Fatalf("authority = %q, want %q", refs[0].Authority, formula.FrozenFormulaAuthorityPackPayload)
	}
	if refs[0].PackName != "extension-pack" || refs[0].ComponentKind != string(ComponentKindCustomField) || refs[0].BusinessKey != "Issue.SubjectEcho" {
		t.Fatalf("unexpected component context = %#v", refs[0])
	}
	if refs[0].ObjectAPIName != "Issue" || refs[0].FieldAPIName != "SubjectEcho" || refs[0].Schema == nil {
		t.Fatalf("unexpected field ref = %#v", refs[0])
	}
	if err := verifyFrozenFormulaPayloadRefs(refs); err != nil {
		t.Fatalf("verifyFrozenFormulaPayloadRefs: %v", err)
	}
}

func TestFrozenFormulaRefsFromObjectSpecComponentRejectsUnparsedPayload(t *testing.T) {
	component := &sourceBackedObjectSpecComponent{
		packName: "jira-like-sample",
		manifestComponent: ManifestComponent{
			Kind:          ComponentKindObjectSpec,
			BusinessKey:   "issue",
			Manageability: ManageabilityLocked,
		},
		payload: []byte(`[{"apiName":"Issue"}]`),
	}
	if _, err := frozenFormulaRefsFromObjectSpecComponent(component); err == nil {
		t.Fatal("frozenFormulaRefsFromObjectSpecComponent = nil err, want parse-state failure")
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentRejectsUnsupportedFunction(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "ResolverFrozen", "label": "Resolver Frozen", "type": "string", "formulaMode": "frozen", "formula": "UNSUPPORTED_FUNC(Subject)", "returnType": "string"}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := verifyFrozenFormulaPayloadForObjectSpecComponent(component)
	if err == nil {
		t.Fatal("verifyFrozenFormulaPayloadForObjectSpecComponent = nil, want runtime-function rejection")
	}
	if !strings.Contains(err.Error(), "Issue.ResolverFrozen") || !strings.Contains(err.Error(), "component issue") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentRejectsSchemaRuntimeMismatch(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"invoice": testObjectSpecPayloadWithFields("Invoice",
			`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "Amount", "label": "Amount", "type": "currency"}`,
			`{"apiName": "AccountId", "label": "Account", "type": "reference", "referenceTo": ["Account"], "relationshipName": "Account"}`,
			`{"apiName": "LowercaseName", "label": "Lowercase Name", "type": "string", "formulaMode": "frozen", "formula": "LEFT(name, 3)", "returnType": "string"}`,
			`{"apiName": "TerminalAccount", "label": "Terminal Account", "type": "string", "formulaMode": "frozen", "formula": "LEFT(Account, 3)", "returnType": "string"}`,
			`{"apiName": "BadOperator", "label": "Bad Operator", "type": "string", "formulaMode": "frozen", "formula": "LEFT(Amount + Name, 3)", "returnType": "string"}`,
			`{"apiName": "BadFunctionArg", "label": "Bad Function Arg", "type": "boolean", "formulaMode": "frozen", "formula": "CONTAINS(Amount, \"2\")", "returnType": "boolean"}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := verifyFrozenFormulaPayloadForObjectSpecComponent(component)
	if err == nil {
		t.Fatal("verifyFrozenFormulaPayloadForObjectSpecComponent = nil, want schema/runtime parity rejection")
	}
	for _, want := range []string{"Invoice.LowercaseName", "Invoice.TerminalAccount", "Invoice.BadOperator", "Invoice.BadFunctionArg"} {
		if !strings.Contains(err.Error(), want) {
			t.Fatalf("error = %v; want offending formula field %s", err, want)
		}
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentRejectsTargetReturnTypeMismatch(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"invoice": testObjectSpecPayloadWithFields("Invoice",
			`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "NameAsBoolean", "label": "Name As Boolean", "type": "boolean", "formulaMode": "frozen", "formula": "Name", "returnType": "boolean"}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := verifyFrozenFormulaPayloadForObjectSpecComponent(component)
	if err == nil {
		t.Fatal("verifyFrozenFormulaPayloadForObjectSpecComponent = nil, want target return type rejection")
	}
	if !strings.Contains(err.Error(), "Invoice.NameAsBoolean") || !strings.Contains(err.Error(), "formula result type") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentRejectsRelationshipTargetTypeMismatch(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"account": testObjectSpecPayloadWithFields("Account",
			`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
		),
		"invoice": testObjectSpecPayloadWithFields("Invoice",
			`{"apiName": "Name", "label": "Name", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "AccountId", "label": "Account", "type": "reference", "referenceTo": ["Account"], "relationshipName": "Account"}`,
			`{"apiName": "RoundedAccountName", "label": "Rounded Account Name", "type": "currency", "formulaMode": "frozen", "formula": "ROUND(Account.Name, 0)", "returnType": "currency"}`,
		),
	}).components[1].(*sourceBackedObjectSpecComponent)

	err := verifyFrozenFormulaPayloadForObjectSpecComponent(component)
	if err == nil {
		t.Fatal("verifyFrozenFormulaPayloadForObjectSpecComponent = nil, want relationship target type rejection")
	}
	if !strings.Contains(err.Error(), "Invoice.RoundedAccountName") || !strings.Contains(err.Error(), "ROUND argument 1 requires number, got string") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentSkipsActiveModeAndEmptyFormula(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "ActiveOnly", "label": "Active Only", "type": "formula", "formulaMode": "active", "formula": "BLANKVALUE(Subject, \"n/a\")", "returnType": "string"}`,
			`{"apiName": "FrozenEmpty", "label": "Frozen Empty", "type": "string", "formulaMode": "frozen", "formula": "", "returnType": "string"}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	if err := verifyFrozenFormulaPayloadForObjectSpecComponent(component); err != nil {
		t.Fatalf("verifyFrozenFormulaPayloadForObjectSpecComponent = %v, want nil", err)
	}
}

func TestVerifyFrozenFormulaPayloadForObjectSpecComponentFailsClosedWhenDisabled(t *testing.T) {
	t.Setenv("DISABLE_FORMULA_VALIDATION", "true")
	component := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := verifyFrozenFormulaPayloadForObjectSpecComponent(component)
	if err == nil {
		t.Fatal("verifyFrozenFormulaPayloadForObjectSpecComponent = nil, want disabled fail-closed error")
	}
	if !strings.Contains(err.Error(), "DISABLE_FORMULA_VALIDATION=true") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestSourceBackedObjectSpecComponentVerifyComponentFailsBeforeLiveChecks(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"issue": testObjectSpecPayloadWithFields("Issue",
			`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
			`{"apiName": "BadFrozen", "label": "Bad Frozen", "type": "string", "formulaMode": "frozen", "formula": "IF(BLANK(", "returnType": "string"}`,
		),
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := component.VerifyComponent(context.Background(), VerifyDeps{})
	if err == nil {
		t.Fatal("VerifyComponent = nil, want payload-proof failure")
	}
	if !strings.Contains(err.Error(), "pack payload write-materialized-formula verification failed") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestSourceBackedObjectSpecComponentVerifyComponentRejectsReadSourceAuthoring(t *testing.T) {
	component := testSourceBackedPack(t, nil, map[string]string{
		"recordtype": `[
  {
    "apiName": "RecordType",
    "label": "RecordType",
    "tableType": "standard_object",
    "category": "system",
    "sharingModel": "PublicRead",
    "enableHierarchySharing": false,
    "description": "RecordType source-backed contract",
    "readSource": {
      "kind": "physical_table",
      "physicalTable": "_System_RecordType"
    },
    "fields": [
      {"apiName": "Name", "label": "Name", "type": "string"}
    ]
  }
]`,
	}).components[0].(*sourceBackedObjectSpecComponent)

	err := component.VerifyComponent(context.Background(), VerifyDeps{})
	if err == nil {
		t.Fatal("VerifyComponent = nil, want readSource authoring rejection")
	}
	if !strings.Contains(err.Error(), "not yet supported in the current pack contract") {
		t.Fatalf("unexpected error = %v", err)
	}
}

func TestPackLifecycleFormulaProofDoesNotCallLiveOwnerPackHelper(t *testing.T) {
	repoRoot := repoRootFromThisFile(t)
	for _, rel := range []string{
		"packs/source_backed_pack.go",
		"source_pack_ops.go",
		"operations.go",
	} {
		raw, err := os.ReadFile(filepath.Join(repoRoot, rel))
		if err != nil {
			t.Fatalf("read %s: %v", rel, err)
		}
		text := string(raw)
		if strings.Contains(text, "ValidateFrozenFormulasForPack(") {
			t.Fatalf("%s still calls ValidateFrozenFormulasForPack; pack proof must come from source-backed object_spec payloads", rel)
		}
	}
}

func frozenFormulaSchemaHasField(schema *models.ObjectMetadata, apiName string) bool {
	if schema == nil {
		return false
	}
	for _, field := range schema.Fields {
		if field.APIName == apiName {
			return true
		}
	}
	return false
}

// TestSourceBackedPack_AppendsConnectorInboundRouteComponent is a regression
// guard for the rebase-regression blocker on PR #1599 (NCRM-INT-10 round-2
// dry-run): the ConnectorInboundRoute case in buildComponents was missing
// `p.components = append(p.components, comp)` after accumulating the
// EventKind. Without the append the component is parsed but never installed,
// applied, or verified — silent loss on any connector pack that declares
// connector_inbound_route entries.
//
// This test verifies that after NewSourceBackedPack, p.Components() contains
// exactly the declared connector_inbound_route component.
func TestSourceBackedPack_AppendsConnectorInboundRouteComponent(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/connector-inbound-route-regression",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "acme-connector",
			DisplayName:           "Acme Connector",
			Category:              ConnectorPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindConnectorInboundRoute,
				BusinessKey:   JoinComponentBusinessKey("acme-connector", "contact.created"),
				Manageability: ManageabilityLocked,
				Payload:       []byte(`{"namespace":"acme-connector","eventKind":"contact.created","flowDeveloperName":"ContactCreatedHandler"}`),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}
	components := pack.Components()
	if got := len(components); got != 1 {
		t.Fatalf("Components() len = %d, want 1 — connector_inbound_route component was not appended to p.components (regression: missing append after EventKind accumulation)", got)
	}
	if got := components[0].Kind(); got != ComponentKindConnectorInboundRoute {
		t.Errorf("components[0].Kind() = %q, want %q", got, ComponentKindConnectorInboundRoute)
	}
	wantBK := JoinComponentBusinessKey("acme-connector", "contact.created")
	if got := components[0].BusinessKey(); got != wantBK {
		t.Errorf("components[0].BusinessKey() = %q, want %q", got, wantBK)
	}
}

// TestSourceBackedPack_RejectsWebhookSecretComponentForNonConnectorPack verifies
// that a pack with category=app-pack cannot declare webhook_secret components —
// the runtime verifier only resolves connector-pack-owned secrets, so allowing
// app-packs to declare them would silently create dead metadata.
func TestSourceBackedPack_RejectsWebhookSecretComponentForNonConnectorPack(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/app-pack-with-webhook-secret",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "app-pack-sample",
			DisplayName:           "App Pack Sample",
			Category:              AppPack, // not connector-pack
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindWebhookSecret,
				BusinessKey:   "app-pack-sample/order-events",
				Manageability: ManageabilityLocked,
				Payload:       []byte(`{"name":"order-events","algorithm":"HMAC-SHA256"}`),
			}},
		},
	}
	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("expected error for webhook_secret in non-connector-pack, got nil")
	}
	if !strings.Contains(err.Error(), "connector-pack") {
		t.Errorf("error message = %q; want it to mention %q", err.Error(), "connector-pack")
	}
}

func TestSourceBackedPackRejectsSearchAllDefaultFieldsMissingField(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/search-default-fields-invalid-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "search-default-fields-invalid-pack",
			DisplayName:           "Search Default Fields Invalid Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityLocked,
					Payload:       []byte(testObjectSpecPayload("Issue")),
				},
				{
					Kind:          ComponentKindSearchAllDefaultFields,
					BusinessKey:   "issue-search-defaults",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`[{"objectApiName":"Issue","fields":["Id","Bogus__c"]}]`),
					DependsOn:     []ComponentReference{{Kind: ComponentKindObjectSpec, BusinessKey: "issue"}},
				},
			},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack succeeded, want missing search_all_default_fields field rejection")
	}
	if got := err.Error(); !strings.Contains(got, "search_all_default_fields") || !strings.Contains(got, "Bogus__c") {
		t.Fatalf("NewSourceBackedPack error = %q, want search_all_default_fields Bogus__c validation failure", got)
	}
}

func TestSourceBackedPackRejectsSearchAllDefaultFieldsUnsearchableFields(t *testing.T) {
	for _, tc := range []struct {
		name  string
		field string
	}{
		{name: "number", field: "AnnualRevenue"},
		{name: "date", field: "ClosedDate"},
		{name: "formula", field: "FormulaText__c"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			source := &ResolvedPackSource{
				Kind: PackSourceKindLocalPath,
				URI:  "/tmp/search-default-fields-disallowed-pack",
				Manifest: &PackManifest{
					ManifestFormatVersion: SupportedManifestFormatVersion,
					Name:                  "search-default-fields-disallowed-pack",
					DisplayName:           "Search Default Fields Disallowed Pack",
					Category:              AppPack,
					Version:               "0.1.0",
					PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
					Components: []ManifestComponent{
						{
							Kind:          ComponentKindObjectSpec,
							BusinessKey:   "issue",
							Manageability: ManageabilityLocked,
							Payload: []byte(testObjectSpecPayloadWithFields("Issue",
								`{"apiName":"Subject","label":"Subject","type":"string","required":true,"isNameField":true}`,
								`{"apiName":"ReporterEmail","label":"Reporter Email","type":"email"}`,
								`{"apiName":"Phone","label":"Phone","type":"phone"}`,
								`{"apiName":"Company","label":"Company","type":"string"}`,
								`{"apiName":"Description","label":"Description","type":"textarea"}`,
								`{"apiName":"AnnualRevenue","label":"Annual Revenue","type":"double"}`,
								`{"apiName":"ClosedDate","label":"Closed Date","type":"date"}`,
								`{"apiName":"FormulaText__c","label":"Formula Text","type":"formula","returnType":"string"}`,
							)),
						},
						{
							Kind:          ComponentKindSearchAllDefaultFields,
							BusinessKey:   "issue-search-defaults",
							Manageability: ManageabilityLocked,
							Payload:       []byte(`[{"objectApiName":"Issue","fields":["` + tc.field + `"]}]`),
							DependsOn:     []ComponentReference{{Kind: ComponentKindObjectSpec, BusinessKey: "issue"}},
						},
					},
				},
			}

			_, err := NewSourceBackedPack(source)
			if err == nil {
				t.Fatalf("NewSourceBackedPack succeeded for search_all_default_fields declared %s field %q, want validation rejection", tc.name, tc.field)
			}
			if got := err.Error(); !strings.Contains(got, "search_all_default_fields") || !strings.Contains(got, tc.field) {
				t.Fatalf("NewSourceBackedPack error = %q, want search_all_default_fields %q validation failure", got, tc.field)
			}
		})
	}
}

func TestSourceBackedPackRejectsObjectSpecSearchAllDefaultFieldsSurface(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/search-default-fields-object-spec-surface-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "search-default-fields-object-spec-surface-pack",
			DisplayName:           "Search Default Fields Object Spec Surface Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "issue",
				Manageability: ManageabilityLocked,
				Payload: []byte(`[
					{
						"apiName": "Issue",
						"label": "Issue",
						"tableType": "standard_object",
						"category": "jira",
						"sharingModel": "Private",
						"enableHierarchySharing": false,
						"description": "Issue work item",
						"searchAllDefaultFields": ["Id", "Subject"],
						"fields": [
							{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}
						]
					}
				]`),
			}},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack succeeded, want object_spec searchAllDefaultFields surface rejection")
	}
	if got := err.Error(); !strings.Contains(got, "searchAllDefaultFields") || !strings.Contains(got, "search_all_default_fields") {
		t.Fatalf("NewSourceBackedPack error = %q, want searchAllDefaultFields/search_all_default_fields rejection", got)
	}
}

func TestSourceBackedPackRejectsWideAllFallbackWithoutSearchDefaultDeclaration(t *testing.T) {
	fieldSpecs := []string{`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`}
	for i := 0; i < searchmetadata.DefaultAllLegacyFallbackFieldCap+1; i++ {
		fieldSpecs = append(fieldSpecs, fmt.Sprintf(`{"apiName":"Text%d__c","label":"Text %d","type":"string"}`, i, i))
	}
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/search-default-fields-wide-undeclared-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "search-default-fields-wide-undeclared-pack",
			DisplayName:           "Search Default Fields Wide Undeclared Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "wide",
				Manageability: ManageabilityLocked,
				Payload:       []byte(testObjectSpecPayloadWithFields("Wide__c", fieldSpecs...)),
			}},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack succeeded, want undeclared wide ALL fallback rejection")
	}
	if got := err.Error(); !strings.Contains(got, "Wide__c") || !strings.Contains(got, "search_all_default_fields") || !strings.Contains(got, "cap") {
		t.Fatalf("NewSourceBackedPack error = %q, want object/cap/default-fields requirement", got)
	}
}

func TestSourceBackedPackRejectsWideAllFallbackWithSystemListFieldsWithoutSearchDefaultDeclaration(t *testing.T) {
	fieldSpecs := []string{`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`}
	for i := 0; i < searchmetadata.DefaultAllLegacyFallbackFieldCap-3; i++ {
		fieldSpecs = append(fieldSpecs, fmt.Sprintf(`{"apiName":"Text%d__c","label":"Text %d","type":"string"}`, i, i))
	}
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/search-default-fields-wide-system-list-fields-pack",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "search-default-fields-wide-system-list-fields-pack",
			DisplayName:           "Search Default Fields Wide System List Fields Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "wide",
				Manageability: ManageabilityLocked,
				Payload: []byte(testObjectSpecPayloadWithListFields("Wide__c", []string{
					constants.FieldOwnerID,
					constants.FieldCreatedByID,
					constants.FieldLastModifiedByID,
				}, fieldSpecs...)),
			}},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack succeeded, want undeclared wide ALL fallback rejection including listFields")
	}
	if got := err.Error(); !strings.Contains(got, "Wide__c") || !strings.Contains(got, "search_all_default_fields") || !strings.Contains(got, "cap") {
		t.Fatalf("NewSourceBackedPack error = %q, want object/listFields cap/default-fields requirement", got)
	}
}

func TestBuildRuntimeDeclarationCapturesSourceBackedPackSlices(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/runtime-declaration-builder",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "runtime-declaration-builder-pack",
			DisplayName:           "Runtime Declaration Builder Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityLocked,
					Payload: []byte(testObjectSpecPayloadWithFields("Issue",
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "FileLinks__c", "label": "File Links", "type": "string"}`,
					)),
				},
				{
					Kind:          ComponentKindPermission,
					BusinessKey:   "feature-permissions",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`[{"apiName":" RunReports ","label":"  Run Reports  ","category":" Feature ","description":" Allows running reports ","requires":" ViewSetup "}]`),
				},
				{
					Kind:          ComponentKindValidationRule,
					BusinessKey:   "issue-required",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(`{"validationRules":[{
						"id":"vrule_issue_subject_required",
						"objectApiName":"Issue",
						"name":"Issue_Subject_Required",
						"active":true,
						"condition":"ISBLANK(Subject)",
						"errorMessage":"Subject is required"
					}]}`),
				},
				{
					Kind:          ComponentKindListFields,
					BusinessKey:   "issue-list-fields",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`{"Issue":["Status","Subject"]}`),
				},
				{
					Kind:          ComponentKindRelatedListRule,
					BusinessKey:   "Issue/Comment__c",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(`{
						"rules": [{
							"parentObjectApiName": "Issue",
							"childObjectApiName": "Comment__c",
							"lookupField": "IssueId__c",
							"bucket": "recommended",
							"fields": ["Subject"]
						}]
					}`),
				},
				{
					Kind:          ComponentKindSearchScopeOrder,
					BusinessKey:   "issue-search-order",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`[{"name":"Issue","label":"Issue"}]`),
				},
				{
					Kind:          ComponentKindHrefRewriteFields,
					BusinessKey:   "issue-file-hrefs",
					Manageability: ManageabilityLocked,
					Payload:       []byte(`{"fields":[{"parentApiName":"Issue","fieldApiName":"FileLinks__c"}]}`),
					DependsOn:     []ComponentReference{{Kind: ComponentKindObjectSpec, BusinessKey: "issue"}},
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	decl := buildRuntimeDeclaration(pack)
	if got := len(decl.objectSpecPayloads); got != 1 {
		t.Fatalf("runtime declaration object payload count = %d, want 1", got)
	}
	specs, err := ParseObjectSpecsPayloads(decl.objectSpecPayloads, "runtime-declaration-test")
	if err != nil {
		t.Fatalf("ParseObjectSpecsPayloads: %v", err)
	}
	if got := len(specs); got != 1 {
		t.Fatalf("runtime declaration object spec count = %d, want 1", got)
	}
	if got := specs[0].APIName; got != "Issue" {
		t.Fatalf("runtime declaration object spec apiName = %q, want %q", got, "Issue")
	}

	if got := len(decl.relatedListRules); got != 1 {
		t.Fatalf("runtime declaration related-list rule count = %d, want 1", got)
	}
	rule := decl.relatedListRules[0]
	if rule.ParentObjectAPI != "Issue" || rule.ChildObjectAPI != "Comment__c" || rule.LookupField != "IssueId__c" {
		t.Fatalf("runtime declaration related-list rule = %+v", rule)
	}

	if got := decl.searchScopeOrder; len(got) != 1 || got[0].Name != "Issue" || got[0].Label != "Issue" {
		t.Fatalf("runtime declaration search scope order = %+v, want one Issue row", got)
	}
	if got := decl.hrefRewriteFields; len(got) != 1 || got[0].ParentApiName != "Issue" || got[0].FieldApiName != "FileLinks__c" {
		t.Fatalf("runtime declaration href rewrite fields = %+v, want Issue/FileLinks__c", got)
	}

	permissionProjection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowPermissionProjection{
		APIName:     " RunReports ",
		Label:       "  Run Reports  ",
		Category:    " Feature ",
		Description: " Allows running reports ",
		Requires:    " ViewSetup ",
		OwnerPack:   "runtime-declaration-builder-pack",
		IsSystem:    true,
		IsActive:    true,
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(permission): %v", err)
	}
	validationRuleProjection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowValidationRuleProjection{
		ObjectAPIName: "Issue",
		Name:          "Issue_Subject_Required",
		Active:        true,
		Condition:     "ISBLANK(Subject)",
		ErrorMessage:  "Subject is required",
		OwnerPack:     "runtime-declaration-builder-pack",
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(validation_rule): %v", err)
	}
	listFieldsProjection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{"Status", "Subject"},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	hrefProjection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowHrefRewriteFieldProjection{
		ParentApiName: "Issue",
		FieldApiName:  "FileLinks__c",
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(href_rewrite_fields): %v", err)
	}
	wantStartupRows := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindHrefRewriteFields, BusinessKey: JoinComponentBusinessKey("Issue", "FileLinks__c"), Projection: hrefProjection},
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: listFieldsProjection},
		{Kind: ComponentKindPermission, BusinessKey: "RunReports", Projection: permissionProjection},
		{Kind: ComponentKindValidationRule, BusinessKey: JoinComponentBusinessKey("Issue", "Issue_Subject_Required"), Projection: validationRuleProjection},
	}
	if !slices.Equal(decl.startupShallowRows, wantStartupRows) {
		t.Fatalf("runtime declaration startup shallow rows = %#v, want %#v", decl.startupShallowRows, wantStartupRows)
	}
}

// TestSourceBackedPackExcludesInactiveValidationRuleFromDeclaration verifies that
// an inactive validation rule (active:false) is not written into the startup-shallow
// declaration. The verify live predicate uses (Active=1 OR IsCustomized=1), so
// declaring an inactive rule would produce a false "is missing" drift on every
// reset-db apply where the rule is installed with Active=0.
func TestSourceBackedPackExcludesInactiveValidationRuleFromDeclaration(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/inactive-vr-regression",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "sample-pack",
			DisplayName:           "Sample Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindValidationRule,
				BusinessKey:   "sample-rules",
				Manageability: ManageabilityDefaultsOnly,
				Payload: []byte(`{"validationRules":[
					{
						"id":"vr_active",
						"objectApiName":"Task",
						"name":"RequireSubject",
						"active":true,
						"condition":"ISBLANK(Subject)",
						"errorMessage":"Subject is required"
					},
					{
						"id":"vr_inactive",
						"objectApiName":"Task",
						"name":"RequireStatus",
						"active":false,
						"condition":"ISBLANK(Status)",
						"errorMessage":"Status is required"
					}
				]}`),
			}},
		},
	}
	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}
	decl := buildRuntimeDeclaration(pack)
	var vrRows []runtimeStartupShallowDeclaredRow
	for _, row := range decl.startupShallowRows {
		if row.Kind == ComponentKindValidationRule {
			vrRows = append(vrRows, row)
		}
	}
	if got := len(vrRows); got != 1 {
		t.Fatalf("startup shallow validation_rule declaration count = %d, want 1 (inactive rule must not be declared)", got)
	}
	if got := vrRows[0].BusinessKey; got != JoinComponentBusinessKey("Task", "RequireSubject") {
		t.Errorf("declared validation_rule business key = %q, want %q", got, JoinComponentBusinessKey("Task", "RequireSubject"))
	}
}

func TestSourceBackedConnectorFieldMappingDeclaresFieldMappingStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/connector-field-mapping-startup-shallow",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "sample-connector",
			DisplayName:           "Sample Connector",
			Category:              ConnectorPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindConnectorFieldMapping,
				BusinessKey:   "sample-connector/contact-sync",
				Manageability: ManageabilityDefaultsOnly,
				Payload: []byte(`[
					{
						"sourceObject": "Contact",
						"targetObject": "Person",
						"actionType": "syncContact",
						"direction": "inbound",
						"mappings": [
							{"sourceField": "email", "targetField": "Email"}
						]
					},
					{
						"sourceObject": "Contact",
						"targetObject": "Person",
						"actionType": "syncContact",
						"direction": "bidirectional",
						"mappings": [
							{"sourceField": "email", "targetField": "Email"}
						]
					}
				]`),
			}},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	wantKeys := []runtimeStartupShallowDeclaredRow{
		{
			Kind:        ComponentKindFieldMapping,
			BusinessKey: JoinComponentBusinessKey("Contact", "Person", "syncContact", "email", "bidirectional"),
		},
		{
			Kind:        ComponentKindFieldMapping,
			BusinessKey: JoinComponentBusinessKey("Contact", "Person", "syncContact", "email", "inbound"),
		},
	}
	got := buildRuntimeDeclaration(pack).startupShallowRows
	if len(got) != 3 || got[0].Kind != ComponentKindConnectorFieldMapping ||
		got[0].BusinessKey != "sample-connector/contact-sync" ||
		strings.TrimSpace(got[0].Projection) == "" ||
		!slices.Equal(got[1:], wantKeys) {
		t.Fatalf("startup shallow rows = %#v, want exact component projection plus %#v", got, wantKeys)
	}
	raw, err := marshalRuntimeStartupShallowDeclaredRows(got)
	if err != nil {
		t.Fatal(err)
	}
	selected, outbound, err := LoadPersistedConnectorFieldMappingDirections(
		raw,
		"sample-connector",
		"sample-connector/contact-sync",
	)
	if err != nil || len(selected) != 1 || selected[0].Direction != connector.DirectionInbound ||
		len(outbound) != 1 || outbound[0].Direction != connector.DirectionBidirectional {
		t.Fatalf("persisted connector mapping directions = inbound %#v, outbound %#v, %v", selected, outbound, err)
	}
}

func TestSourceBackedPackDeclaresObjectSpecDefaultListFieldsStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-list-fields-declaration",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-list-fields-pack",
			DisplayName:           "Object Spec List Fields Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "issue",
				Manageability: ManageabilityLocked,
				Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{"Subject", "Status"},
					`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
					`{"apiName": "Status", "label": "Status", "type": "picklist"}`,
				)),
			}},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{"Subject", "Status"},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: projection},
	}
	if got := buildRuntimeDeclaration(pack).startupShallowRows; !slices.Equal(got, want) {
		t.Fatalf("startup shallow rows = %#v, want %#v", got, want)
	}
}

func TestSourceBackedPackDeclaresObjectSpecEmptyListFieldsStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-empty-list-fields-declaration",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-empty-list-fields-pack",
			DisplayName:           "Object Spec Empty List Fields Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "issue",
				Manageability: ManageabilityLocked,
				Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{},
					`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
				)),
			}},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: projection},
	}
	if got := buildRuntimeDeclaration(pack).startupShallowRows; !slices.Equal(got, want) {
		t.Fatalf("startup shallow rows = %#v, want declared empty list_fields projection %#v", got, want)
	}
}

func TestSourceBackedPackOmittedObjectSpecListFieldsStaysUndeclaredStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-omitted-list-fields-declaration",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-omitted-list-fields-pack",
			DisplayName:           "Object Spec Omitted List Fields Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{{
				Kind:          ComponentKindObjectSpec,
				BusinessKey:   "issue",
				Manageability: ManageabilityLocked,
				Payload: []byte(testObjectSpecPayloadWithFields("Issue",
					`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
				)),
			}},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	if got := buildRuntimeDeclaration(pack).startupShallowRows; len(got) != 0 {
		t.Fatalf("startup shallow rows = %#v, want omitted listFields to stay undeclared", got)
	}
}

func TestSourceBackedPackExplicitListFieldsOverridesObjectSpecDefaultStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-list-fields-override",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-list-fields-override-pack",
			DisplayName:           "Object Spec List Fields Override Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{"Subject"},
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "Status", "label": "Status", "type": "picklist"}`,
					)),
				},
				{
					Kind:          ComponentKindListFields,
					BusinessKey:   "issue-list-fields",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`{"Issue":["Status"]}`),
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{"Status"},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: projection},
	}
	if got := buildRuntimeDeclaration(pack).startupShallowRows; !slices.Equal(got, want) {
		t.Fatalf("startup shallow rows = %#v, want explicit list_fields projection %#v", got, want)
	}
}

func TestSourceBackedPackListFieldsOverrideAppliesAfterDefaultsOnlyObjectSpec(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-list-fields-override-order",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-list-fields-override-order-pack",
			DisplayName:           "Object Spec List Fields Override Order Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindListFields,
					BusinessKey:   "issue-list-fields",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`{"Issue":["Status"]}`),
				},
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{"Subject"},
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "Status", "label": "Status", "type": "picklist"}`,
					)),
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	plan, err := pack.InstallPlan()
	if err != nil {
		t.Fatalf("InstallPlan: %v", err)
	}
	components := plan.ComponentsForPhase(InstallPlanPhaseObjectMetadata)
	var objectSpecIndex, listFieldsIndex = -1, -1
	for i, component := range components {
		if component.Kind() == ComponentKindObjectSpec && component.BusinessKey() == "issue" {
			objectSpecIndex = i
		}
		if component.Kind() == ComponentKindListFields && component.BusinessKey() == "issue-list-fields" {
			listFieldsIndex = i
		}
	}
	if objectSpecIndex < 0 || listFieldsIndex < 0 {
		t.Fatalf("components = %#v, want object_spec issue and list_fields issue-list-fields", components)
	}
	if objectSpecIndex > listFieldsIndex {
		t.Fatalf("component order object_spec=%d list_fields=%d, want list_fields override to apply after defaults_only object_spec", objectSpecIndex, listFieldsIndex)
	}

	projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{"Status"},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: projection},
	}
	if got := buildRuntimeDeclaration(pack).startupShallowRows; !slices.Equal(got, want) {
		t.Fatalf("startup shallow rows = %#v, want explicit list_fields projection %#v", got, want)
	}
}

func TestSourceBackedPackRepairReplaysListFieldsOverrideAfterObjectSpecRepair(t *testing.T) {
	component, err := newSourceBackedListFieldsComponent("crm", ManifestComponent{
		Kind:          ComponentKindListFields,
		BusinessKey:   "issue-list-fields",
		Manageability: ManageabilityDefaultsOnly,
	}, []byte(`{"Issue":["Status"]}`))
	if err != nil {
		t.Fatalf("newSourceBackedListFieldsComponent: %v", err)
	}
	pack := &SourceBackedPack{components: []PackComponent{component}}

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()
	mock.ExpectExec("UPDATE `_System_Object` SET `ListFields` = \\?, `ListFieldsCustomized` = 0, `LastModifiedDate` = NOW\\(\\) WHERE `ApiName` = \\? AND \\(`ListFieldsCustomized` = 0 OR `ListFieldsCustomized` IS NULL\\)").
		WithArgs(`["Status"]`, "Issue").
		WillReturnResult(sqlmock.NewResult(0, 1))

	if err := pack.replayListFieldsComponentsForObjects(context.Background(), ApplyDeps{DB: db}, map[string]bool{"issue": true}); err != nil {
		t.Fatalf("replayListFieldsComponentsForObjects: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestSourceBackedPackExplicitEmptyListFieldsOverridesObjectSpecDefaultStartupShallow(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-list-fields-empty-override",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-list-fields-empty-override-pack",
			DisplayName:           "Object Spec List Fields Empty Override Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityDefaultsOnly,
					Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{"Subject"},
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
					)),
				},
				{
					Kind:          ComponentKindListFields,
					BusinessKey:   "issue-empty-list-fields",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`{"Issue":[]}`),
				},
			},
		},
	}

	pack, err := NewSourceBackedPack(source)
	if err != nil {
		t.Fatalf("NewSourceBackedPack: %v", err)
	}

	projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
		ObjectAPIName: "Issue",
		Fields:        []string{},
	})
	if err != nil {
		t.Fatalf("marshalRuntimeStartupShallowProjection(list_fields): %v", err)
	}
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindListFields, BusinessKey: "Issue", Projection: projection},
	}
	if got := buildRuntimeDeclaration(pack).startupShallowRows; !slices.Equal(got, want) {
		t.Fatalf("startup shallow rows = %#v, want explicit empty list_fields projection %#v", got, want)
	}
}

func TestSourceBackedPackRejectsListFieldsOverrideForLockedObjectSpecListFields(t *testing.T) {
	source := &ResolvedPackSource{
		Kind: PackSourceKindLocalPath,
		URI:  "/tmp/object-spec-list-fields-locked-conflict",
		Manifest: &PackManifest{
			ManifestFormatVersion: SupportedManifestFormatVersion,
			Name:                  "object-spec-list-fields-locked-conflict-pack",
			DisplayName:           "Object Spec List Fields Locked Conflict Pack",
			Category:              AppPack,
			Version:               "0.1.0",
			PlatformCompatibility: PlatformCompatibility{Min: "0.1.0", MaxExclusive: "0.2.0"},
			Components: []ManifestComponent{
				{
					Kind:          ComponentKindObjectSpec,
					BusinessKey:   "issue",
					Manageability: ManageabilityLocked,
					Payload: []byte(testObjectSpecPayloadWithListFields("Issue", []string{"Subject"},
						`{"apiName": "Subject", "label": "Subject", "type": "string", "required": true, "isNameField": true}`,
						`{"apiName": "Status", "label": "Status", "type": "picklist"}`,
					)),
				},
				{
					Kind:          ComponentKindListFields,
					BusinessKey:   "issue-list-fields",
					Manageability: ManageabilityDefaultsOnly,
					Payload:       []byte(`{"Issue":["Status"]}`),
				},
			},
		},
	}

	_, err := NewSourceBackedPack(source)
	if err == nil {
		t.Fatal("NewSourceBackedPack succeeded, want locked object_spec listFields conflict")
	}
	if !strings.Contains(err.Error(), "conflicts with locked object_spec listFields") {
		t.Fatalf("NewSourceBackedPack error = %v, want locked object_spec listFields conflict", err)
	}
}

func TestNormalizeRuntimeStartupShallowDeclaredRowsSortsAndDedupes(t *testing.T) {
	rows := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindValidationRule, BusinessKey: " Issue/SubjectRequired "},
		{Kind: ComponentKindPermission, BusinessKey: "RunReports", Projection: ` {"b":2} `},
		{Kind: ComponentKindValidationRule, BusinessKey: "Issue/SubjectRequired"},
		{Kind: ComponentKindPermission, BusinessKey: "RunReports", Projection: `{"a":1}`},
		{Kind: "", BusinessKey: "ignored"},
		{Kind: ComponentKindPermission, BusinessKey: ""},
	}

	got := normalizeRuntimeStartupShallowDeclaredRows(rows)
	want := []runtimeStartupShallowDeclaredRow{
		{Kind: ComponentKindPermission, BusinessKey: "RunReports", Projection: `{"a":1}`},
		{Kind: ComponentKindValidationRule, BusinessKey: "Issue/SubjectRequired"},
	}
	if !slices.Equal(got, want) {
		t.Fatalf("normalizeRuntimeStartupShallowDeclaredRows() = %#v, want %#v", got, want)
	}
}

func connectedAppComponentManifestPayload() string {
	return `{
        "id": "synthetic-client",
        "name": "Synthetic Client",
        "consumerKey": "synthetic-client-key",
        "consumerSecret": "synthetic-secret",
        "callbackUrl": "https://client.example/callback",
        "redirectUris": ["https://client.example/callback"],
        "clientType": "confidential",
        "scopes": ["api"],
        "isActive": true,
        "tokenEndpointAuthMethod": "client_secret_post",
        "allowedGrantTypes": ["authorization_code", "password"],
        "consentPolicy": "trusted",
        "consentVersion": 1,
        "requirePkce": false,
        "issueRefreshTokens": false,
        "permittedUsers": "AllUsers"
      }`
}

func connectedAppPublicScopeManifestPayload() string {
	return `{
        "id": "oauthscope_external_records_read",
        "name": "external.records.read",
        "description": "Read-only app-pack record access for public OAuth clients.",
        "allowed_client_types": ["public"],
        "allowed_methods": ["GET", "HEAD"],
        "route_family_prefix": "/services/data/", "route_patterns": ["/services/data", "/services/data/:version/sobjects"],
        "resource_links": ["sobjects"],
        "active": true
      }`
}

func connectedAppPublicComponentManifestPayload() string {
	return `{
        "id": "synthetic-public-client",
        "name": "Synthetic Public Client",
        "consumerKey": "synthetic-public-client-key",
        "callbackUrl": "https://client.example/callback",
        "redirectUris": ["https://client.example/callback"],
        "clientType": "public",
        "scopes": ["external.records.read"],
        "isActive": true,
        "tokenEndpointAuthMethod": "none",
        "allowedGrantTypes": ["authorization_code"],
        "consentPolicy": "trusted",
        "consentVersion": 1,
        "requirePkce": true,
        "issueRefreshTokens": false,
        "permittedUsers": "AllUsers"
      }`
}
