package catalogsnapshot

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"math"
	"regexp"
	"slices"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/nexuscrm/kernel/pkg/schemautil"
	"github.com/nexuscrm/shared/pkg/models"
	layoutauthoringv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/layoutauthoringv1"
)

var storedJSONNumberPattern = regexp.MustCompile(`^(-?)(0|[1-9][0-9]*)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?$`)

// layoutAuthoringDocument is the immutable stored-authoring projection. It is
// deliberately separate from pageLayoutResolutionDocument: the latter is a
// caller-effective rendering projection, while this document preserves the
// complete stored aggregate that an authoring client may write back.
type layoutAuthoringDocument struct {
	Workspaces []layoutAuthoringWorkspaceRecord `json:"workspaces"`
}

type layoutAuthoringWorkspaceRecord struct {
	CreateContext     *layoutauthoringv1.CreateContext            `json:"createContext,omitempty"`
	ObjectID          string                                      `json:"objectId"`
	WorkspaceVersion  string                                      `json:"workspaceVersion"`
	OrderedListFields []string                                    `json:"orderedListFields"`
	Layouts           []layoutAuthoringLayoutRecord               `json:"layouts"`
	Assignments       []layoutauthoringv1.LayoutAssignmentSummary `json:"assignments"`
}

type layoutAuthoringLayoutRecord struct {
	Document         layoutauthoringv1.LayoutDocument       `json:"document"`
	Lifecycle        layoutauthoringv1.LayoutLifecycleState `json:"lifecycle"`
	ResourceVersion  string                                 `json:"resourceVersion"`
	AggregateVersion string                                 `json:"aggregateVersion"`
}

type layoutAuthoringResourceVersionFacts struct {
	Document  layoutauthoringv1.LayoutDocument       `json:"document"`
	Lifecycle layoutauthoringv1.LayoutLifecycleState `json:"lifecycle"`
}

type layoutAuthoringAggregateVersionFacts struct {
	Document          layoutauthoringv1.LayoutDocument `json:"document"`
	OrderedListFields []string                         `json:"orderedListFields"`
}

type layoutAuthoringWorkspaceVersionFacts struct {
	ObjectID          string                                      `json:"objectId"`
	OrderedListFields []string                                    `json:"orderedListFields"`
	Layouts           []layoutAuthoringLayoutRecord               `json:"layouts"`
	Assignments       []layoutauthoringv1.LayoutAssignmentSummary `json:"assignments"`
}

// LayoutAuthoringVersions is the Catalog-owned optimistic-concurrency view of
// one locked Layout workspace. Runtime compares command preconditions against
// these values before applying a mutation in the same transaction; it must not
// invent a second digest implementation.
type LayoutAuthoringVersions struct {
	WorkspaceVersion           string
	LayoutResourceVersions     map[string]string
	LayoutAggregateVersions    map[string]string
	AssignmentResourceVersions map[string]string
}

// ComputeLayoutAuthoringVersions derives the exact versions emitted by the
// immutable authoring projection from an already locked Core aggregate.
func ComputeLayoutAuthoringVersions(
	objectID string,
	orderedListFields []string,
	layouts []*models.PageLayout,
	assignments []*models.LayoutAssignment,
) (LayoutAuthoringVersions, error) {
	const syntheticRevision CatalogRevision = 1
	object := &models.ObjectMetadata{APIName: objectID, ListFields: cloneStrings(orderedListFields)}
	layoutInputs := make([]PageLayoutInput, 0, len(layouts))
	for _, layout := range layouts {
		layoutInputs = append(layoutInputs, PageLayoutInput{CatalogRevision: syntheticRevision, Layout: layout})
	}
	assignmentInputs := make([]LayoutAssignmentInput, 0, len(assignments))
	for _, assignment := range assignments {
		assignmentInputs = append(assignmentInputs, LayoutAssignmentInput{
			CatalogRevision: syntheticRevision, Assignment: assignment,
		})
	}
	document, err := buildLayoutAuthoringDocument(
		syntheticRevision,
		[]ObjectInput{{CatalogRevision: syntheticRevision, Metadata: object}},
		layoutInputs,
		assignmentInputs,
	)
	if err != nil {
		return LayoutAuthoringVersions{}, err
	}
	if len(document.Workspaces) != 1 {
		return LayoutAuthoringVersions{}, fmt.Errorf("Layout authoring workspace %q is absent", objectID)
	}
	workspace := document.Workspaces[0]
	versions := LayoutAuthoringVersions{
		WorkspaceVersion:           workspace.WorkspaceVersion,
		LayoutResourceVersions:     make(map[string]string, len(workspace.Layouts)),
		LayoutAggregateVersions:    make(map[string]string, len(workspace.Layouts)),
		AssignmentResourceVersions: make(map[string]string, len(workspace.Assignments)),
	}
	for _, layout := range workspace.Layouts {
		versions.LayoutResourceVersions[layout.Document.LayoutID] = layout.ResourceVersion
		versions.LayoutAggregateVersions[layout.Document.LayoutID] = layout.AggregateVersion
	}
	for _, assignment := range workspace.Assignments {
		versions.AssignmentResourceVersions[assignment.AssignmentID] = assignment.ResourceVersion
	}
	return versions, nil
}

func inputHasLayoutAuthoringProjection(values []RuntimeProjectionInput) bool {
	for _, value := range values {
		if value.Profile == ProjectionLayoutAuthoringV1 {
			return true
		}
	}
	return false
}

func validateLayoutAuthoringProjectionBinding(capabilityID string, definitionComponentIDs []string) error {
	if capabilityID != layoutauthoringv1.CapabilityName {
		return fmt.Errorf("Layout authoring profile requires capability %q", layoutauthoringv1.CapabilityName)
	}
	if len(definitionComponentIDs) != 0 {
		return fmt.Errorf("Layout authoring profile cannot select executable definitions")
	}
	return nil
}

func buildLayoutAuthoringDocumentFromAggregates(
	revision CatalogRevision,
	inputs []LayoutAuthoringAggregateInput,
	catalogObjects map[string]*models.ObjectMetadata,
	mutation *mutationDefinitionDocument,
) (*layoutAuthoringDocument, error) {
	var err error
	catalogObjects, err = createInputObjects(catalogObjects, mutation)
	if err != nil {
		return nil, err
	}
	objects := make([]ObjectInput, 0, len(inputs))
	layouts := []PageLayoutInput{}
	assignments := []LayoutAssignmentInput{}
	seenObjects := make(map[string]string, len(inputs))
	for index, input := range inputs {
		path := fmt.Sprintf("layoutAuthoring.aggregates[%d]", index)
		if err := requireSourceRevision(path, revision, input.CatalogRevision); err != nil {
			return nil, err
		}
		if input.Aggregate == nil {
			return nil, conflict(ConflictInvalidDefinition, path, "stored Layout aggregate is nil")
		}
		aggregate := input.Aggregate
		if aggregate.OrderedListFields == nil || aggregate.Layouts == nil || aggregate.Assignments == nil {
			return nil, conflict(ConflictInvalidDefinition, path, "stored Layout aggregate is incomplete")
		}
		if err := requireCatalogObjectAPIName(path+".objectId", aggregate.ObjectAPIName); err != nil {
			return nil, err
		}
		key := foldIdentity(aggregate.ObjectAPIName)
		catalogObject := catalogObjects[key]
		if catalogObject == nil {
			return nil, conflict(ConflictInvalidDefinition, path+".objectId", "object %q is absent", aggregate.ObjectAPIName)
		}
		if aggregate.ObjectAPIName != catalogObject.APIName {
			return nil, conflict(
				ConflictInvalidDefinition, path+".objectId",
				"stored object identity %q is not canonical; expected %q", aggregate.ObjectAPIName, catalogObject.APIName,
			)
		}
		if previous, duplicate := seenObjects[key]; duplicate {
			return nil, conflict(
				ConflictInvalidDefinition, path+".objectId",
				"stored Layout aggregate %q duplicates %q case-insensitively", aggregate.ObjectAPIName, previous,
			)
		}
		seenObjects[key] = aggregate.ObjectAPIName
		objects = append(objects, ObjectInput{
			CatalogRevision: revision,
			Metadata: &models.ObjectMetadata{
				APIName: aggregate.ObjectAPIName, ListFields: cloneStrings(aggregate.OrderedListFields),
				Fields: catalogObject.Fields,
			},
		})
		for layoutIndex, layout := range aggregate.Layouts {
			if layout != nil && layout.ObjectAPIName != aggregate.ObjectAPIName {
				return nil, conflict(
					ConflictInvalidDefinition, fmt.Sprintf("%s.layouts[%d].objectId", path, layoutIndex),
					"stored Layout belongs to %q; expected %q", layout.ObjectAPIName, aggregate.ObjectAPIName,
				)
			}
			layouts = append(layouts, PageLayoutInput{CatalogRevision: revision, Layout: layout})
		}
		for assignmentIndex, assignment := range aggregate.Assignments {
			if assignment != nil && assignment.ObjectAPIName != aggregate.ObjectAPIName {
				return nil, conflict(
					ConflictInvalidDefinition, fmt.Sprintf("%s.assignments[%d].objectId", path, assignmentIndex),
					"stored Layout assignment belongs to %q; expected %q", assignment.ObjectAPIName, aggregate.ObjectAPIName,
				)
			}
			assignments = append(assignments, LayoutAssignmentInput{CatalogRevision: revision, Assignment: assignment})
		}
	}
	if len(seenObjects) != len(catalogObjects) {
		missing := make([]string, 0, len(catalogObjects)-len(seenObjects))
		for key, object := range catalogObjects {
			if _, present := seenObjects[key]; !present {
				missing = append(missing, object.APIName)
			}
		}
		sort.Slice(missing, func(i, j int) bool { return identityLess(missing[i], missing[j]) })
		return nil, conflict(
			ConflictInvalidDefinition, "layoutAuthoring.aggregates",
			"stored Layout aggregates are missing objects %q", strings.Join(missing, ", "),
		)
	}
	return buildLayoutAuthoringDocument(revision, objects, layouts, assignments)
}

func buildLayoutAuthoringDocument(
	revision CatalogRevision,
	objects []ObjectInput,
	layouts []PageLayoutInput,
	assignments []LayoutAssignmentInput,
) (*layoutAuthoringDocument, error) {
	workspaces := make(map[string]*layoutAuthoringWorkspaceRecord, len(objects))
	globalLayoutIDs := make(map[string]string, len(layouts))
	globalAssignmentIDs := make(map[string]string, len(assignments))
	defaultLayoutByWorkspace := make(map[string]string)
	for index, input := range assignments {
		if input.Assignment == nil {
			continue
		}
		assignment := input.Assignment
		if assignment.ProfileID != nil || assignment.RoleID != nil || assignment.RecordTypeID != nil {
			continue
		}
		workspaceKey := foldIdentity(assignment.ObjectAPIName)
		if previous := defaultLayoutByWorkspace[workspaceKey]; previous != "" {
			return nil, conflict(
				ConflictInvalidDefinition, fmt.Sprintf("layoutAuthoring.assignments[%d]", index),
				"wildcard Layout %q duplicates wildcard Layout %q", assignment.LayoutID, previous,
			)
		}
		defaultLayoutByWorkspace[workspaceKey] = assignment.LayoutID
	}
	for index, input := range objects {
		path := fmt.Sprintf("layoutAuthoring.objects[%d]", index)
		if err := requireSourceRevision(path, revision, input.CatalogRevision); err != nil {
			return nil, err
		}
		if input.Metadata == nil {
			return nil, conflict(ConflictInvalidDefinition, path, "object metadata is nil")
		}
		objectID := input.Metadata.APIName
		if err := requireCatalogObjectAPIName(path+".objectId", objectID); err != nil {
			return nil, err
		}
		key := foldIdentity(objectID)
		workspaces[key] = &layoutAuthoringWorkspaceRecord{
			CreateContext: &layoutauthoringv1.CreateContext{RequiredFields: schemautil.RequiredCreateInputFields(input.Metadata)},
			ObjectID:      objectID, OrderedListFields: cloneStrings(input.Metadata.ListFields),
			Layouts: []layoutAuthoringLayoutRecord{}, Assignments: []layoutauthoringv1.LayoutAssignmentSummary{},
		}
	}

	for index, input := range layouts {
		path := fmt.Sprintf("layoutAuthoring.layouts[%d]", index)
		if err := requireSourceRevision(path, revision, input.CatalogRevision); err != nil {
			return nil, err
		}
		if input.Layout == nil {
			return nil, conflict(ConflictInvalidDefinition, path, "layout is nil")
		}
		if input.Layout.IsDeleted {
			continue
		}
		layoutIdentity := foldIdentity(input.Layout.ID)
		if previous, duplicate := globalLayoutIDs[layoutIdentity]; duplicate {
			return nil, conflict(ConflictInvalidDefinition, path+".layoutId", "layout %q duplicates %q case-insensitively", input.Layout.ID, previous)
		}
		globalLayoutIDs[layoutIdentity] = input.Layout.ID
		workspace := workspaces[foldIdentity(input.Layout.ObjectAPIName)]
		if workspace == nil {
			return nil, conflict(ConflictInvalidDefinition, path+".objectId", "object %q is absent", input.Layout.ObjectAPIName)
		}
		document, err := projectStoredLayoutDocument(*input.Layout)
		if err != nil {
			return nil, conflict(ConflictInvalidDefinition, path+".document", "%v", err)
		}
		generationVersion, err := projectStoredUint32("generationVersion", input.Layout.GenerationVersion)
		if err != nil {
			return nil, conflict(ConflictInvalidDefinition, path+".lifecycle", "%v", err)
		}
		lifecycle := layoutauthoringv1.LayoutLifecycleState{
			ProvenanceKind:    layoutAuthoringProvenance(*input.Layout),
			GenerationVersion: generationVersion,
			Customized:        input.Layout.IsCustomized,
			IsDefault:         strings.EqualFold(input.Layout.ID, defaultLayoutByWorkspace[foldIdentity(input.Layout.ObjectAPIName)]),
			CreatedAt:         formatOptionalTime(input.Layout.CreatedDate),
			LastModifiedAt:    formatOptionalTime(input.Layout.LastModifiedDate),
		}
		resourceVersion, err := layoutAuthoringLayoutResourceVersion(document, lifecycle)
		if err != nil {
			return nil, conflict(ConflictEncoding, path+".resourceVersion", "%v", err)
		}
		aggregateVersion, err := layoutAuthoringLayoutAggregateVersion(document, workspace.OrderedListFields)
		if err != nil {
			return nil, conflict(ConflictEncoding, path+".aggregateVersion", "%v", err)
		}
		workspace.Layouts = append(workspace.Layouts, layoutAuthoringLayoutRecord{
			Document: document, Lifecycle: lifecycle,
			ResourceVersion: resourceVersion, AggregateVersion: aggregateVersion,
		})
	}

	for index, input := range assignments {
		path := fmt.Sprintf("layoutAuthoring.assignments[%d]", index)
		if err := requireSourceRevision(path, revision, input.CatalogRevision); err != nil {
			return nil, err
		}
		if input.Assignment == nil {
			return nil, conflict(ConflictInvalidDefinition, path, "assignment is nil")
		}
		assignment := *input.Assignment
		assignmentIdentity := foldIdentity(assignment.ID)
		if previous, duplicate := globalAssignmentIDs[assignmentIdentity]; duplicate {
			return nil, conflict(ConflictInvalidDefinition, path+".assignmentId", "assignment %q duplicates %q case-insensitively", assignment.ID, previous)
		}
		globalAssignmentIDs[assignmentIdentity] = assignment.ID
		workspace := workspaces[foldIdentity(assignment.ObjectAPIName)]
		if workspace == nil {
			return nil, conflict(ConflictInvalidDefinition, path+".objectId", "object %q is absent", assignment.ObjectAPIName)
		}
		expectedSpecificity := int(layoutAssignmentSpecificity(assignment))
		if assignment.Specificity != expectedSpecificity {
			return nil, conflict(
				ConflictInvalidDefinition, path+".specificity",
				"stored specificity %d does not match selector-derived specificity %d", assignment.Specificity, expectedSpecificity,
			)
		}
		summary := layoutauthoringv1.LayoutAssignmentSummary{
			AssignmentID: assignment.ID, ObjectID: assignment.ObjectAPIName, LayoutID: assignment.LayoutID,
			Selectors: layoutauthoringv1.AssignmentSelectors{
				ProfileID: cloneStringPointer(assignment.ProfileID), RoleID: cloneStringPointer(assignment.RoleID),
				RecordTypeID: cloneStringPointer(assignment.RecordTypeID),
			},
			Specificity: layoutAssignmentSpecificity(assignment),
			CreatedAt:   formatOptionalTime(assignment.CreatedDate), LastModifiedAt: formatOptionalTime(assignment.LastModifiedDate),
			CreatedByID: cloneStringPointer(assignment.CreatedByID),
		}
		version, err := layoutAuthoringAssignmentResourceVersion(summary)
		if err != nil {
			return nil, conflict(ConflictEncoding, path+".resourceVersion", "%v", err)
		}
		summary.ResourceVersion = version
		workspace.Assignments = append(workspace.Assignments, summary)
	}

	document := &layoutAuthoringDocument{Workspaces: make([]layoutAuthoringWorkspaceRecord, 0, len(workspaces))}
	for _, workspace := range workspaces {
		sort.Slice(workspace.Layouts, func(i, j int) bool {
			return identityLess(workspace.Layouts[i].Document.LayoutID, workspace.Layouts[j].Document.LayoutID)
		})
		sort.Slice(workspace.Assignments, func(i, j int) bool {
			return workspace.Assignments[i].AssignmentID < workspace.Assignments[j].AssignmentID
		})
		version, err := layoutAuthoringWorkspaceVersion(*workspace)
		if err != nil {
			return nil, conflict(ConflictEncoding, "layoutAuthoring.workspaceVersion", "%v", err)
		}
		workspace.WorkspaceVersion = version
		document.Workspaces = append(document.Workspaces, *workspace)
	}
	sort.Slice(document.Workspaces, func(i, j int) bool {
		return identityLess(document.Workspaces[i].ObjectID, document.Workspaces[j].ObjectID)
	})
	if err := validateLayoutAuthoringDocument(document); err != nil {
		return nil, conflict(ConflictInvalidDefinition, "layoutAuthoring", "%v", err)
	}
	return document, nil
}

func cloneLayoutCreateContext(context *layoutauthoringv1.CreateContext) *layoutauthoringv1.CreateContext {
	if context == nil {
		return nil
	}
	return &layoutauthoringv1.CreateContext{RequiredFields: cloneStrings(context.RequiredFields)}
}

// Context is a versioned projection of the source schema, never an authored fact.
// Older artifacts retain absence; new artifacts must carry even an empty list.
func validateLayoutCreateContextVersion(document *layoutAuthoringDocument, required bool) error {
	if document == nil {
		return nil
	}
	for _, workspace := range document.Workspaces {
		if (workspace.CreateContext != nil) != required {
			return fmt.Errorf("object %q Layout create context does not match the artifact schema", workspace.ObjectID)
		}
	}
	return nil
}

func validateLayoutCreateContextSource(document *layoutAuthoringDocument, objects map[string]*models.ObjectMetadata, mutation *mutationDefinitionDocument) error {
	if document == nil {
		return nil
	}
	var err error
	objects, err = createInputObjects(objects, mutation)
	if err != nil {
		return err
	}
	for _, workspace := range document.Workspaces {
		if workspace.CreateContext == nil {
			continue
		}
		object := objects[foldIdentity(workspace.ObjectID)]
		if object == nil || !slices.Equal(workspace.CreateContext.RequiredFields, schemautil.RequiredCreateInputFields(object)) {
			return fmt.Errorf("object %q Layout create context differs from its source schema", workspace.ObjectID)
		}
	}
	return nil
}

func projectLayoutAuthoring(source *layoutAuthoringDocument, included map[string]struct{}) *layoutAuthoringDocument {
	result := &layoutAuthoringDocument{Workspaces: []layoutAuthoringWorkspaceRecord{}}
	if source == nil {
		return result
	}
	for _, workspace := range source.Workspaces {
		if _, ok := included[foldIdentity(workspace.ObjectID)]; ok {
			result.Workspaces = append(result.Workspaces, workspace)
		}
	}
	return result
}

func validateLayoutAuthoringDocument(document *layoutAuthoringDocument) error {
	if document == nil || document.Workspaces == nil {
		return fmt.Errorf("document and workspaces are required")
	}
	previousObject := ""
	globalLayoutIDs := map[string]string{}
	globalAssignmentIDs := map[string]string{}
	for index, workspace := range document.Workspaces {
		path := fmt.Sprintf("workspaces[%d]", index)
		if workspace.CreateContext != nil {
			if err := layoutauthoringv1.ValidateCreateContext(*workspace.CreateContext); err != nil {
				return fmt.Errorf("%s.createContext: %w", path, err)
			}
		}
		if err := requireCatalogObjectAPIName(path+".objectId", workspace.ObjectID); err != nil {
			return err
		}
		if index > 0 && !identityLess(previousObject, workspace.ObjectID) {
			return fmt.Errorf("workspaces are not canonical")
		}
		previousObject = workspace.ObjectID
		if workspace.OrderedListFields == nil || workspace.Layouts == nil || workspace.Assignments == nil || workspace.WorkspaceVersion == "" {
			return fmt.Errorf("%s is incomplete", path)
		}
		layoutIDs := make(map[string]struct{}, len(workspace.Layouts))
		previousLayout := ""
		defaultLayout := ""
		for layoutIndex, layout := range workspace.Layouts {
			if err := layoutauthoringv1.ValidateLayoutDocument(layout.Document); err != nil {
				return fmt.Errorf("%s.layouts[%d].document: %w", path, layoutIndex, err)
			}
			if layout.Document.ObjectID != workspace.ObjectID || layout.ResourceVersion == "" || layout.AggregateVersion == "" ||
				(layoutIndex > 0 && !identityLess(previousLayout, layout.Document.LayoutID)) {
				return fmt.Errorf("%s.layouts[%d] is inconsistent", path, layoutIndex)
			}
			expectedResourceVersion, err := layoutAuthoringLayoutResourceVersion(layout.Document, layout.Lifecycle)
			if err != nil || layout.ResourceVersion != expectedResourceVersion {
				return fmt.Errorf("%s.layouts[%d].resourceVersion is inconsistent", path, layoutIndex)
			}
			expectedAggregateVersion, err := layoutAuthoringLayoutAggregateVersion(layout.Document, workspace.OrderedListFields)
			if err != nil || layout.AggregateVersion != expectedAggregateVersion {
				return fmt.Errorf("%s.layouts[%d].aggregateVersion is inconsistent", path, layoutIndex)
			}
			viewRef, receipts := layoutAuthoringValidationReceipts()
			stored := layoutauthoringv1.StoredLayout{
				CreateContext: workspace.CreateContext,
				Document:      layout.Document, OrderedListFields: workspace.OrderedListFields,
				Lifecycle: layout.Lifecycle, ResourceVersion: layout.ResourceVersion,
				AggregateVersion: layout.AggregateVersion, WorkspaceVersion: workspace.WorkspaceVersion,
				ViewRef: viewRef, Receipts: receipts,
			}
			if err := layoutauthoringv1.ValidateStoredLayoutResponse(
				layoutauthoringv1.StoredLayoutRequest{LayoutID: layout.Document.LayoutID, ViewRef: &viewRef}, stored,
			); err != nil {
				return fmt.Errorf("%s.layouts[%d] violates the authoring contract: %w", path, layoutIndex, err)
			}
			foldedLayoutID := foldIdentity(layout.Document.LayoutID)
			if previous, duplicate := globalLayoutIDs[foldedLayoutID]; duplicate {
				return fmt.Errorf("%s.layouts[%d] duplicates Layout identity %q", path, layoutIndex, previous)
			}
			if _, duplicate := layoutIDs[foldedLayoutID]; duplicate {
				return fmt.Errorf("%s.layouts[%d] duplicates a case-insensitive Layout identity", path, layoutIndex)
			}
			previousLayout = layout.Document.LayoutID
			layoutIDs[foldedLayoutID] = struct{}{}
			globalLayoutIDs[foldedLayoutID] = layout.Document.LayoutID
			if layout.Lifecycle.IsDefault {
				if defaultLayout != "" {
					return fmt.Errorf("%s contains multiple defaults", path)
				}
				defaultLayout = layout.Document.LayoutID
			}
		}
		previousAssignment := ""
		wildcardLayout := ""
		selectorTuples := map[string]struct{}{}
		for assignmentIndex, assignment := range workspace.Assignments {
			if assignment.ObjectID != workspace.ObjectID || assignment.AssignmentID == "" || assignment.ResourceVersion == "" ||
				(assignmentIndex > 0 && assignment.AssignmentID <= previousAssignment) {
				return fmt.Errorf("%s.assignments[%d] is inconsistent", path, assignmentIndex)
			}
			foldedAssignmentID := foldIdentity(assignment.AssignmentID)
			if previous, duplicate := globalAssignmentIDs[foldedAssignmentID]; duplicate {
				return fmt.Errorf("%s.assignments[%d] duplicates assignment identity %q", path, assignmentIndex, previous)
			}
			globalAssignmentIDs[foldedAssignmentID] = assignment.AssignmentID
			expectedResourceVersion, err := layoutAuthoringAssignmentResourceVersion(assignment)
			if err != nil || assignment.ResourceVersion != expectedResourceVersion {
				return fmt.Errorf("%s.assignments[%d].resourceVersion is inconsistent", path, assignmentIndex)
			}
			if _, ok := layoutIDs[foldIdentity(assignment.LayoutID)]; !ok {
				return fmt.Errorf("%s.assignments[%d] references absent layout", path, assignmentIndex)
			}
			previousAssignment = assignment.AssignmentID
			tuple := layoutAssignmentSelectorTuple(assignment.Selectors)
			if _, duplicate := selectorTuples[tuple]; duplicate {
				return fmt.Errorf("%s contains duplicate assignment selectors", path)
			}
			selectorTuples[tuple] = struct{}{}
			if assignment.Selectors.ProfileID == nil && assignment.Selectors.RoleID == nil && assignment.Selectors.RecordTypeID == nil {
				wildcardLayout = assignment.LayoutID
			}
		}
		if !strings.EqualFold(defaultLayout, wildcardLayout) {
			return fmt.Errorf("%s default and wildcard assignment disagree", path)
		}
		expectedWorkspaceVersion, err := layoutAuthoringWorkspaceVersion(workspace)
		if err != nil || workspace.WorkspaceVersion != expectedWorkspaceVersion {
			return fmt.Errorf("%s.workspaceVersion is inconsistent", path)
		}
		viewRef, receipts := layoutAuthoringValidationReceipts()
		response := layoutauthoringv1.Workspace{
			ObjectID: workspace.ObjectID, WorkspaceVersion: workspace.WorkspaceVersion,
			Layouts:     make([]layoutauthoringv1.LayoutSummary, 0, len(workspace.Layouts)),
			Assignments: workspace.Assignments, ViewRef: viewRef, Receipts: receipts,
		}
		for _, layout := range workspace.Layouts {
			response.Layouts = append(response.Layouts, layoutauthoringv1.LayoutSummary{
				LayoutID: layout.Document.LayoutID, ObjectID: layout.Document.ObjectID,
				Name: layout.Document.Name, Kind: layout.Document.Kind, IsDefault: layout.Lifecycle.IsDefault,
				ResourceVersion: layout.ResourceVersion, CreatedAt: layout.Lifecycle.CreatedAt,
				LastModifiedAt: layout.Lifecycle.LastModifiedAt,
			})
		}
		if err := layoutauthoringv1.ValidateWorkspaceResponse(
			layoutauthoringv1.WorkspaceRequest{ObjectID: workspace.ObjectID, ViewRef: &viewRef}, response,
		); err != nil {
			return fmt.Errorf("%s violates the authoring contract: %w", path, err)
		}
	}
	return nil
}

func layoutAuthoringValidationReceipts() (layoutauthoringv1.CatalogViewRef, layoutauthoringv1.Receipts) {
	viewRef := layoutauthoringv1.CatalogViewRef{
		CatalogRevision:     1,
		AuthoringViewDigest: "sha256:" + strings.Repeat("0", 64),
	}
	return viewRef, layoutauthoringv1.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: 1,
		CapabilityVersion: layoutauthoringv1.CapabilityName,
	}
}

// ProjectStoredLayoutDocument is the single stored PageLayout -> neutral
// authoring document conversion. Runtime command execution uses the same owner
// as Catalog projection when applying patches and deriving postconditions.
func ProjectStoredLayoutDocument(layout models.PageLayout) (layoutauthoringv1.LayoutDocument, error) {
	layoutVersion, err := projectStoredUint32("layoutVersion", layout.LayoutVersion)
	if err != nil {
		return layoutauthoringv1.LayoutDocument{}, err
	}
	document := layoutauthoringv1.LayoutDocument{
		LayoutID: layout.ID, ObjectID: layout.ObjectAPIName, Name: layout.LayoutName, Kind: layout.Type,
		LayoutVersion: layoutVersion, CompactFields: cloneStrings(layout.CompactLayout),
		Tabs: cloneStrings(layout.Tabs), Sections: make([]layoutauthoringv1.LayoutSection, 0, len(layout.Sections)),
		RelatedLists:         make([]layoutauthoringv1.RelatedList, 0, len(layout.RelatedLists)),
		RecordPanels:         make([]layoutauthoringv1.RecordPanel, 0, len(layout.RecordPanels)),
		HeaderActions:        layoutauthoringv1.ActionPlacement{Authored: layout.HeaderActionsAuthored || len(layout.HeaderActions) != 0, Actions: []layoutauthoringv1.LayoutAction{}},
		QuickActions:         layoutauthoringv1.ActionPlacement{Authored: layout.QuickActionsAuthored || len(layout.QuickActions) != 0, Actions: []layoutauthoringv1.LayoutAction{}},
		RecordStates:         make([]layoutauthoringv1.RecordStateRule, 0, len(layout.RecordStates)),
		PrimaryHeaderActions: cloneStrings(layout.PrimaryHeaderActions),
	}
	for _, section := range layout.Sections {
		columns, err := projectStoredUint32("section "+section.ID+" columns", section.Columns)
		if err != nil {
			return layoutauthoringv1.LayoutDocument{}, err
		}
		configuration, err := projectNamedValues(section.ComponentConfig)
		if err != nil {
			return layoutauthoringv1.LayoutDocument{}, fmt.Errorf("section %q component configuration: %w", section.ID, err)
		}
		labels := make([]layoutauthoringv1.FieldLabel, 0, len(section.FieldLabels))
		for field, label := range section.FieldLabels {
			labels = append(labels, layoutauthoringv1.FieldLabel{FieldReference: field, Label: label})
		}
		sort.Slice(labels, func(i, j int) bool { return labels[i].FieldReference < labels[j].FieldReference })
		behaviors := make([]layoutauthoringv1.FieldBehaviorGroup, 0, len(section.FieldBehaviors))
		for field, modes := range section.FieldBehaviors {
			group := layoutauthoringv1.FieldBehaviorGroup{FieldReference: field}
			if modes != nil {
				entries := make([]layoutauthoringv1.FieldBehavior, 0, len(modes))
				for mode, behavior := range modes {
					entries = append(entries, layoutauthoringv1.FieldBehavior{Mode: mode, Behavior: behavior})
				}
				sort.Slice(entries, func(i, j int) bool { return entries[i].Mode < entries[j].Mode })
				group.Behaviors = &entries
			}
			behaviors = append(behaviors, group)
		}
		sort.Slice(behaviors, func(i, j int) bool { return behaviors[i].FieldReference < behaviors[j].FieldReference })
		document.Sections = append(document.Sections, layoutauthoringv1.LayoutSection{
			SectionID: section.ID, Label: section.Label, TabID: cloneStringPointer(section.Tab), Kind: cloneStringPointer(section.Type),
			ComponentName: cloneStringPointer(section.ComponentName), ComponentConfiguration: configuration,
			Columns: columns, Fields: cloneStrings(section.Fields), FieldLabels: labels,
			FieldBehaviors: behaviors, VisibilityCondition: cloneStringPointer(section.VisibilityCondition),
		})
	}
	for _, list := range layout.RelatedLists {
		mappings := make([]layoutauthoringv1.PrefillMapping, 0, len(list.PrefillMappings))
		for target, source := range list.PrefillMappings {
			mappings = append(mappings, layoutauthoringv1.PrefillMapping{TargetFieldID: target, SourceFieldID: source})
		}
		sort.Slice(mappings, func(i, j int) bool { return mappings[i].TargetFieldID < mappings[j].TargetFieldID })
		document.RelatedLists = append(document.RelatedLists, layoutauthoringv1.RelatedList{
			RelatedListID: list.ID, Label: list.Label, SecondaryLabel: list.SecondaryLabel,
			ObjectID: list.ObjectAPIName, LookupFieldID: list.LookupField,
			FieldIDs: cloneStrings(list.Fields), PrefillMappings: mappings,
		})
	}
	for _, panel := range layout.RecordPanels {
		order, err := projectStoredInt32("panel "+panel.ID+" order", panel.Order)
		if err != nil {
			return layoutauthoringv1.LayoutDocument{}, err
		}
		configuration, err := projectNamedValues(panel.Config)
		if err != nil {
			return layoutauthoringv1.LayoutDocument{}, fmt.Errorf("panel %q configuration: %w", panel.ID, err)
		}
		document.RecordPanels = append(document.RecordPanels, layoutauthoringv1.RecordPanel{
			PanelID: panel.ID, RendererID: panel.RendererID, Position: panel.Position, TabID: panel.TabID,
			TabLabel: panel.TabLabel, Order: order, VisibilityCondition: panel.VisibilityCondition,
			RendererConfiguration: configuration,
		})
	}
	document.HeaderActions.Actions, err = projectActions(layout.HeaderActions)
	if err != nil {
		return layoutauthoringv1.LayoutDocument{}, fmt.Errorf("header actions: %w", err)
	}
	document.QuickActions.Actions, err = projectActions(layout.QuickActions)
	if err != nil {
		return layoutauthoringv1.LayoutDocument{}, fmt.Errorf("quick actions: %w", err)
	}
	for _, rule := range layout.RecordStates {
		priority, err := projectStoredInt32("record state "+rule.ID+" priority", rule.Priority)
		if err != nil {
			return layoutauthoringv1.LayoutDocument{}, err
		}
		projected := layoutauthoringv1.RecordStateRule{
			RuleID: rule.ID, Condition: rule.When, Priority: priority,
			Links:           make([]layoutauthoringv1.RecordStateLink, 0, len(rule.Links)),
			ActionOverrides: make([]layoutauthoringv1.RecordStateActionOverride, 0, len(rule.ActionOverrides)),
		}
		if rule.Banner != nil {
			projected.Banner = &layoutauthoringv1.RecordStateBanner{
				Severity: rule.Banner.Severity, Title: rule.Banner.Title, Body: rule.Banner.Body, LinkIDs: cloneStrings(rule.Banner.LinkIDs),
			}
		}
		for _, link := range rule.Links {
			projected.Links = append(projected.Links, layoutauthoringv1.RecordStateLink{
				LinkID: link.ID, Label: link.Label, ReferenceFieldID: link.ReferenceFieldAPIName,
				ObjectID: link.ObjectAPIName, Placement: link.Placement,
			})
		}
		for _, override := range rule.ActionOverrides {
			projected.ActionOverrides = append(projected.ActionOverrides, layoutauthoringv1.RecordStateActionOverride{
				ActionKey: override.ActionKey, Mode: override.Mode, Reason: override.Reason,
			})
		}
		if rule.ReadOnly != nil {
			projected.ReadOnly = &layoutauthoringv1.RecordStateReadOnly{Enabled: rule.ReadOnly.Enabled, Reason: rule.ReadOnly.Reason}
		}
		document.RecordStates = append(document.RecordStates, projected)
	}
	if err := layoutauthoringv1.ValidateLayoutDocument(document); err != nil {
		return layoutauthoringv1.LayoutDocument{}, err
	}
	return document, nil
}

func projectStoredLayoutDocument(layout models.PageLayout) (layoutauthoringv1.LayoutDocument, error) {
	return ProjectStoredLayoutDocument(layout)
}

func projectActions(values []models.ActionConfig) ([]layoutauthoringv1.LayoutAction, error) {
	result := make([]layoutauthoringv1.LayoutAction, 0, len(values))
	for _, value := range values {
		configuration, err := projectNamedValues(value.Config)
		if err != nil {
			return nil, fmt.Errorf("action %q configuration: %w", value.Name, err)
		}
		extensions, err := projectNamedValues(value.Extensions)
		if err != nil {
			return nil, fmt.Errorf("action %q extensions: %w", value.Name, err)
		}
		result = append(result, layoutauthoringv1.LayoutAction{
			Name: value.Name, Label: value.Label, Kind: value.Type,
			Icon: cloneStringPointer(value.Icon), TargetObjectID: cloneStringPointer(value.TargetObject),
			Component: cloneStringPointer(value.Component), VisibilityCondition: cloneStringPointer(value.VisibilityCondition),
			Configuration: configuration, Extensions: extensions,
		})
	}
	return result, nil
}

func projectNamedValues(values map[string]interface{}) ([]layoutauthoringv1.NamedValue, error) {
	result := make([]layoutauthoringv1.NamedValue, 0, len(values))
	for name, raw := range values {
		value, err := projectValue(raw)
		if err != nil {
			return nil, fmt.Errorf("%q: %w", name, err)
		}
		result = append(result, layoutauthoringv1.NamedValue{Name: name, Value: value})
	}
	sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
	return result, nil
}

func projectValue(raw interface{}) (layoutauthoringv1.Value, error) {
	switch value := raw.(type) {
	case nil:
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueNull}, nil
	case bool:
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueBoolean, Boolean: &value}, nil
	case string:
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueString, String: &value}, nil
	case json.Number:
		decimal, err := plainStoredJSONDecimal(string(value))
		if err != nil {
			return layoutauthoringv1.Value{}, err
		}
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueDecimal, Decimal: &decimal}, nil
	case float64:
		if math.IsInf(value, 0) || math.IsNaN(value) {
			return layoutauthoringv1.Value{}, fmt.Errorf("non-finite JSON number")
		}
		decimal := strconv.FormatFloat(value, 'f', -1, 64)
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueDecimal, Decimal: &decimal}, nil
	case float32:
		if float64Value := float64(value); math.IsInf(float64Value, 0) || math.IsNaN(float64Value) {
			return layoutauthoringv1.Value{}, fmt.Errorf("non-finite JSON number")
		}
		decimal := strconv.FormatFloat(float64(value), 'f', -1, 32)
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueDecimal, Decimal: &decimal}, nil
	case int:
		return decimalValue(strconv.FormatInt(int64(value), 10)), nil
	case int8:
		return decimalValue(strconv.FormatInt(int64(value), 10)), nil
	case int16:
		return decimalValue(strconv.FormatInt(int64(value), 10)), nil
	case int32:
		return decimalValue(strconv.FormatInt(int64(value), 10)), nil
	case int64:
		return decimalValue(strconv.FormatInt(value, 10)), nil
	case uint:
		return decimalValue(strconv.FormatUint(uint64(value), 10)), nil
	case uint8:
		return decimalValue(strconv.FormatUint(uint64(value), 10)), nil
	case uint16:
		return decimalValue(strconv.FormatUint(uint64(value), 10)), nil
	case uint32:
		return decimalValue(strconv.FormatUint(uint64(value), 10)), nil
	case uint64:
		return decimalValue(strconv.FormatUint(value, 10)), nil
	case []interface{}:
		items := make([]layoutauthoringv1.Value, 0, len(value))
		for _, item := range value {
			projected, err := projectValue(item)
			if err != nil {
				return layoutauthoringv1.Value{}, err
			}
			items = append(items, projected)
		}
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueList, List: &items}, nil
	case map[string]interface{}:
		object, err := projectNamedValues(value)
		if err != nil {
			return layoutauthoringv1.Value{}, err
		}
		return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueObject, Object: &object}, nil
	default:
		return layoutauthoringv1.Value{}, fmt.Errorf("unsupported stored JSON value %T", raw)
	}
}

// plainStoredJSONDecimal expands a valid JSON exponent without passing through
// float64. New Layout authoring rows are stored as text and retain their exact
// plain decimal; this expansion is the lossless compatibility path for older
// rows whose native JSON column canonicalized a number to exponent notation.
func plainStoredJSONDecimal(value string) (string, error) {
	match := storedJSONNumberPattern.FindStringSubmatch(value)
	if match == nil {
		return "", fmt.Errorf("invalid stored JSON number %q", value)
	}
	if match[4] == "" {
		if len(value) > layoutauthoringv1.MaxDecimalBytes {
			return "", fmt.Errorf("stored decimal exceeds %d bytes", layoutauthoringv1.MaxDecimalBytes)
		}
		return value, nil
	}
	exponent, err := strconv.ParseInt(match[4], 10, 32)
	if err != nil || exponent > int64(layoutauthoringv1.MaxDecimalBytes) || exponent < -int64(layoutauthoringv1.MaxDecimalBytes) {
		return "", fmt.Errorf("stored JSON exponent is outside the authoring decimal envelope")
	}
	digits := match[2] + match[3]
	decimalPosition := len(match[2]) + int(exponent)
	var unsigned string
	switch {
	case decimalPosition <= 0:
		unsigned = "0." + strings.Repeat("0", -decimalPosition) + digits
	case decimalPosition >= len(digits):
		unsigned = digits + strings.Repeat("0", decimalPosition-len(digits))
	default:
		unsigned = digits[:decimalPosition] + "." + digits[decimalPosition:]
	}
	parts := strings.SplitN(unsigned, ".", 2)
	parts[0] = strings.TrimLeft(parts[0], "0")
	if parts[0] == "" {
		parts[0] = "0"
	}
	unsigned = strings.Join(parts, ".")
	if match[1] == "-" {
		unsigned = "-" + unsigned
	}
	if len(unsigned) > layoutauthoringv1.MaxDecimalBytes {
		return "", fmt.Errorf("expanded stored decimal exceeds %d bytes", layoutauthoringv1.MaxDecimalBytes)
	}
	return unsigned, nil
}

func decimalValue(value string) layoutauthoringv1.Value {
	return layoutauthoringv1.Value{Kind: layoutauthoringv1.ValueDecimal, Decimal: &value}
}

func layoutAuthoringVersion(value interface{}) (string, error) {
	data, err := marshalCanonical(value)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256(data)
	return "sha256:" + hex.EncodeToString(sum[:]), nil
}

func layoutAuthoringLayoutResourceVersion(
	document layoutauthoringv1.LayoutDocument,
	lifecycle layoutauthoringv1.LayoutLifecycleState,
) (string, error) {
	return layoutAuthoringVersion(layoutAuthoringResourceVersionFacts{Document: document, Lifecycle: lifecycle})
}

func layoutAuthoringLayoutAggregateVersion(
	document layoutauthoringv1.LayoutDocument,
	orderedListFields []string,
) (string, error) {
	return layoutAuthoringVersion(layoutAuthoringAggregateVersionFacts{
		Document: document, OrderedListFields: orderedListFields,
	})
}

func layoutAuthoringAssignmentResourceVersion(summary layoutauthoringv1.LayoutAssignmentSummary) (string, error) {
	summary.ResourceVersion = ""
	return layoutAuthoringVersion(summary)
}

func layoutAuthoringWorkspaceVersion(workspace layoutAuthoringWorkspaceRecord) (string, error) {
	return layoutAuthoringVersion(layoutAuthoringWorkspaceVersionFacts{
		ObjectID: workspace.ObjectID, OrderedListFields: workspace.OrderedListFields,
		Layouts: workspace.Layouts, Assignments: workspace.Assignments,
	})
}

func layoutAssignmentSelectorTuple(selectors layoutauthoringv1.AssignmentSelectors) string {
	value := func(pointer *string) string {
		if pointer == nil {
			return ""
		}
		return strings.ToLower(*pointer)
	}
	return value(selectors.ProfileID) + "\x00" + value(selectors.RoleID) + "\x00" + value(selectors.RecordTypeID)
}

func cloneStrings(values []string) []string {
	if len(values) == 0 {
		return []string{}
	}
	return append([]string(nil), values...)
}

func cloneStringPointer(value *string) *string {
	if value == nil {
		return nil
	}
	cloned := *value
	return &cloned
}

func projectStoredUint32(path string, value int) (uint32, error) {
	if value < 0 || uint64(value) > math.MaxUint32 {
		return 0, fmt.Errorf("%s %d is outside uint32", path, value)
	}
	return uint32(value), nil
}

func projectStoredInt32(path string, value int) (int32, error) {
	if int64(value) < math.MinInt32 || int64(value) > math.MaxInt32 {
		return 0, fmt.Errorf("%s %d is outside int32", path, value)
	}
	return int32(value), nil
}

func layoutAssignmentSpecificity(value models.LayoutAssignment) uint32 {
	var specificity uint32
	if value.ProfileID != nil {
		specificity++
	}
	if value.RoleID != nil {
		specificity++
	}
	if value.RecordTypeID != nil {
		specificity++
	}
	return specificity
}

func layoutAuthoringProvenance(layout models.PageLayout) string {
	if strings.TrimSpace(layout.Source) != "" {
		return layout.Source
	}
	if layout.IsCustomized {
		return "legacy_customized"
	}
	return "legacy_unspecified"
}

func formatOptionalTime(value time.Time) string {
	if value.IsZero() {
		return ""
	}
	return value.UTC().Format(time.RFC3339Nano)
}

func (v *RuntimeMetadataView) SupportsLayoutAuthoring() bool {
	return v != nil && v.doc.Profile == ProjectionLayoutAuthoringV1 &&
		(v.doc.Schema == RuntimeMetadataViewSchemaV27 || v.doc.Schema == RuntimeMetadataViewSchemaV28) && v.doc.CapabilityID == layoutauthoringv1.CapabilityName &&
		v.doc.LayoutAuthoring != nil
}

func (v *RuntimeMetadataView) LayoutAuthoringWorkspace(objectID string) (layoutauthoringv1.Workspace, bool) {
	if !v.SupportsLayoutAuthoring() {
		return layoutauthoringv1.Workspace{}, false
	}
	for _, workspace := range v.doc.LayoutAuthoring.Workspaces {
		if !strings.EqualFold(workspace.ObjectID, objectID) {
			continue
		}
		assignments, err := cloneJSON(workspace.Assignments)
		if err != nil {
			return layoutauthoringv1.Workspace{}, false
		}
		result := layoutauthoringv1.Workspace{
			ObjectID: workspace.ObjectID, WorkspaceVersion: workspace.WorkspaceVersion,
			Layouts:     make([]layoutauthoringv1.LayoutSummary, 0, len(workspace.Layouts)),
			Assignments: assignments,
			ViewRef:     v.layoutAuthoringViewRef(), Receipts: v.layoutAuthoringReceipts(),
		}
		for _, layout := range workspace.Layouts {
			result.Layouts = append(result.Layouts, layoutauthoringv1.LayoutSummary{
				LayoutID: layout.Document.LayoutID, ObjectID: layout.Document.ObjectID,
				Name: layout.Document.Name, Kind: layout.Document.Kind, IsDefault: layout.Lifecycle.IsDefault,
				ResourceVersion: layout.ResourceVersion, CreatedAt: layout.Lifecycle.CreatedAt,
				LastModifiedAt: layout.Lifecycle.LastModifiedAt,
			})
		}
		return result, true
	}
	return layoutauthoringv1.Workspace{}, false
}

func (v *RuntimeMetadataView) LayoutAuthoringStoredLayout(layoutID string) (layoutauthoringv1.StoredLayout, bool) {
	if !v.SupportsLayoutAuthoring() {
		return layoutauthoringv1.StoredLayout{}, false
	}
	for _, workspace := range v.doc.LayoutAuthoring.Workspaces {
		for _, layout := range workspace.Layouts {
			if !strings.EqualFold(layout.Document.LayoutID, layoutID) {
				continue
			}
			document, err := cloneJSON(layout.Document)
			if err != nil {
				return layoutauthoringv1.StoredLayout{}, false
			}
			return layoutauthoringv1.StoredLayout{
				CreateContext: cloneLayoutCreateContext(workspace.CreateContext),
				Document:      document, OrderedListFields: cloneStrings(workspace.OrderedListFields),
				Lifecycle: layout.Lifecycle, ResourceVersion: layout.ResourceVersion,
				AggregateVersion: layout.AggregateVersion, WorkspaceVersion: workspace.WorkspaceVersion,
				ViewRef: v.layoutAuthoringViewRef(), Receipts: v.layoutAuthoringReceipts(),
			}, true
		}
	}
	return layoutauthoringv1.StoredLayout{}, false
}

// LayoutAuthoringAssignment resolves one globally unique assignment identity
// from the same immutable authoring view. It exists for typed assignment-delete
// admission, whose public request intentionally carries no caller-authored
// object hint.
func (v *RuntimeMetadataView) LayoutAuthoringAssignment(assignmentID string) (layoutauthoringv1.LayoutAssignmentSummary, bool) {
	if !v.SupportsLayoutAuthoring() {
		return layoutauthoringv1.LayoutAssignmentSummary{}, false
	}
	for _, workspace := range v.doc.LayoutAuthoring.Workspaces {
		for _, assignment := range workspace.Assignments {
			if strings.EqualFold(assignment.AssignmentID, assignmentID) {
				cloned, err := cloneJSON(assignment)
				if err != nil {
					return layoutauthoringv1.LayoutAssignmentSummary{}, false
				}
				return cloned, true
			}
		}
	}
	return layoutauthoringv1.LayoutAssignmentSummary{}, false
}

func (v *RuntimeMetadataView) layoutAuthoringViewRef() layoutauthoringv1.CatalogViewRef {
	return layoutauthoringv1.CatalogViewRef{
		CatalogRevision: uint64(v.doc.Lineage.CatalogRevision), AuthoringViewDigest: string(v.digest),
	}
}

func (v *RuntimeMetadataView) layoutAuthoringReceipts() layoutauthoringv1.Receipts {
	return layoutauthoringv1.Receipts{
		ProtocolVersion: "1.0", CatalogRevision: uint64(v.doc.Lineage.CatalogRevision),
		SchemaGeneration: uint64(v.doc.Lineage.SchemaGeneration), EngineGeneration: uint64(v.doc.Lineage.EngineGeneration),
		CapabilityVersion: layoutauthoringv1.CapabilityName,
	}
}
