import type { AtomConstructorBinding, AtomId, AttachmentOwner, Binding, BoundDependency, Conformance, DependencyBinding, DependencyPort, EdgeCardinality, EdgeDefinition, EdgeEndpoint, EdgeEndpointConstraint, EdgePrimitive, EdgeProjectionId, EdgeTypeId, InterfaceMember, InterfaceOperation, InterfaceOperationMode, InterfaceRevision, InterfaceRevisionId, OperationId, OwnedAttachment, PackageExport, PackageOperationExport, PackageRevision, PackageRevisionId, PersistentAttachment, SlotId, SourceRevision, StatePrimitive, StateSlotDefinition, ValueType, WorkspaceRevision, } from "./types.js"; import { valueType } from "./types.js"; export type CapabilityValidationIssueCode = | "invalid-semantic-major" | "duplicate-conformance-id" | "required-value" | "invalid-source" | "invalid-value-type" | "invalid-operation" | "duplicate-atom" | "duplicate-attachment" | "duplicate-projection" | "duplicate-interface-revision" | "duplicate-interface-member" | "duplicate-interface-operation" | "duplicate-package-revision" | "duplicate-package-export" | "duplicate-dependency-port" | "duplicate-conformance" | "duplicate-operation-binding" | "duplicate-relationship-materialization" | "duplicate-constructor" | "unresolved-reference" | "invalid-attachment" | "invalid-state-binding" | "invalid-edge-binding" | "invalid-package-binding" | "invalid-dependency-binding" | "private-attachment-access" | "incompatible-package-receiver" | "unsatisfied-interface" | "cyclic-conformance-requirement" | "missing-operation-binding" | "unknown-operation-binding" | "invalid-constructor" | "invalid-relationship-materialization"; export interface CapabilityValidationIssue { code: CapabilityValidationIssueCode; path: string; message: string; } interface InterfaceOperationEntry { operation: InterfaceOperation; member: InterfaceMember; } interface InterfaceIndexEntry { revision: InterfaceRevision; operations: Map; } interface PackageIndexEntry { revision: PackageRevision; exports: Map; } interface AttachmentIndexEntry extends OwnedAttachment { path: string; } interface ValidationIndexes { atoms: Map; interfaces: Map; packages: Map; conformances: Map; conformancePaths: Map; attachments: Map; projections: Map< string, { edge: EdgeDefinition; endpoint: EdgeEndpoint; endpointIndex: 0 | 1 } >; constructors: Map; } const hasText = (value: string) => value.trim().length > 0; const conformanceKey = ( atomId: AtomId, interfaceRevisionId: InterfaceRevisionId, ): string => `${atomId}\u0000${interfaceRevisionId}`; const attachmentKey = (attachment: PersistentAttachment): string => `${attachment.kind}\u0000${attachment.id}`; const issue = ( issues: CapabilityValidationIssue[], code: CapabilityValidationIssueCode, path: string, message: string, ) => issues.push({ code, path, message }); const requireText = ( issues: CapabilityValidationIssue[], value: string, path: string, label: string, ) => { if (!hasText(value)) { issue(issues, "required-value", path, `${label} is required`); } }; const typeLabel = (type: ValueType): string => { switch (type.kind) { case "builtin": case "scalar": return type.name; case "message": return `message:${type.descriptorId}`; case "record": return `record{${Object.keys(type.fields).sort().map((key) => `${key}:${typeLabel(type.fields[key])}`).join(";")}}`; case "object-ref": return type.expectation.kind === "atom" ? `object:atom:${type.expectation.atomId}` : `object:interface:${type.expectation.interfaceRevisionId}`; case "optional": return `optional<${typeLabel(type.value)}>`; case "list": return `list<${typeLabel(type.value)}>`; } }; export const valueTypesEqual = (left: ValueType, right: ValueType): boolean => { if (left.kind !== right.kind) { return false; } switch (left.kind) { case "builtin": case "scalar": return left.name === (right as typeof left).name; case "message": return left.descriptorId === (right as typeof left).descriptorId; case "record": { const other = (right as typeof left).fields; return Object.keys(left.fields).length === Object.keys(other).length && Object.entries(left.fields).every(([key, value]) => Object.hasOwn(other, key) && valueTypesEqual(value, other[key])); } case "object-ref": { const other = (right as typeof left).expectation; if (left.expectation.kind !== other.kind) { return false; } return left.expectation.kind === "atom" ? left.expectation.atomId === (other as typeof left.expectation).atomId : left.expectation.interfaceRevisionId === (other as typeof left.expectation).interfaceRevisionId; } case "optional": case "list": return valueTypesEqual(left.value, (right as typeof left).value); } }; const constraintsEqual = ( left: EdgeEndpointConstraint, right: EdgeEndpointConstraint, ) => left.kind === right.kind && (left.kind === "atom" ? left.atomId === (right as typeof left).atomId : left.interfaceRevisionId === (right as typeof left).interfaceRevisionId); const sourceRequiredFields: Array<[keyof SourceRevision, string]> = [ ["repository", "repository"], ["commit", "commit"], ]; const validateSource = ( issues: CapabilityValidationIssue[], source: SourceRevision, path: string, ) => { for (const [key, label] of sourceRequiredFields) { if (!hasText(source[key])) { issue( issues, "invalid-source", `${path}.${key}`, `Source ${label} is required`, ); } } }; const validateValueType = ( issues: CapabilityValidationIssue[], type: ValueType, path: string, indexes: Pick, ) => { switch (type.kind) { case "record": for (const [name, field] of Object.entries(type.fields)) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) issue(issues, "invalid-value-type", path, "Invalid record field name"); validateValueType(issues, field, `${path}.fields.${name}`, indexes); } return; case "builtin": case "scalar": return; case "message": requireText( issues, type.descriptorId, `${path}.descriptorId`, "Message descriptor identity", ); return; case "optional": case "list": validateValueType(issues, type.value, `${path}.value`, indexes); return; case "object-ref": if (type.expectation.kind === "atom") { if (!indexes.atoms.has(type.expectation.atomId)) { issue( issues, "unresolved-reference", `${path}.expectation.atomId`, `Unknown atom ${type.expectation.atomId}`, ); } } else if ( !indexes.interfaces.has(type.expectation.interfaceRevisionId) ) { issue( issues, "unresolved-reference", `${path}.expectation.interfaceRevisionId`, `Unknown interface revision ${type.expectation.interfaceRevisionId}`, ); } } }; const validateConstraint = ( issues: CapabilityValidationIssue[], constraint: EdgeEndpointConstraint, path: string, indexes: Pick, ) => { if (constraint.kind === "atom") { if (!indexes.atoms.has(constraint.atomId)) { issue( issues, "unresolved-reference", `${path}.atomId`, `Unknown atom ${constraint.atomId}`, ); } } else if (!indexes.interfaces.has(constraint.interfaceRevisionId)) { issue( issues, "unresolved-reference", `${path}.interfaceRevisionId`, `Unknown interface revision ${constraint.interfaceRevisionId}`, ); } }; const emitsEvents = (mode: InterfaceOperationMode) => mode === "watch-start" || mode === "subscribe"; const validateOperationEventShape = ( issues: CapabilityValidationIssue[], operation: Pick, path: string, ) => { if (emitsEvents(operation.mode) && operation.eventType === undefined) { issue( issues, "invalid-operation", `${path}.eventType`, `${operation.mode} operations require an event type`, ); } if (!emitsEvents(operation.mode) && operation.eventType !== undefined) { issue( issues, "invalid-operation", `${path}.eventType`, `${operation.mode} operations must not declare an event type`, ); } }; interface OperationSignature { mode: InterfaceOperationMode; inputType: ValueType; outputType: ValueType; eventType?: ValueType; } const signaturesMatch = ( operation: OperationSignature, implementation: OperationSignature, ) => operation.mode === implementation.mode && valueTypesEqual(operation.inputType, implementation.inputType) && valueTypesEqual(operation.outputType, implementation.outputType) && (operation.eventType === undefined ? implementation.eventType === undefined : implementation.eventType !== undefined && valueTypesEqual(operation.eventType, implementation.eventType)); const describeSignature = (signature: OperationSignature) => { const event = signature.eventType ? ` emits ${typeLabel(signature.eventType)}` : ""; return `${signature.mode} ${typeLabel(signature.inputType)} -> ${typeLabel(signature.outputType)}${event}`; }; export const statePrimitiveSignature = ( slot: StateSlotDefinition, primitive: StatePrimitive, ): OperationSignature => { const writeType = slot.storagePolicy.kind === "crdt-document" ? slot.storagePolicy.updateType : slot.valueType; switch (primitive) { case "read": return { mode: "call", inputType: valueType.unit, outputType: slot.valueType, }; case "write": return { mode: "call", inputType: writeType, outputType: valueType.unit, }; case "watch-start": return { mode: "watch-start", inputType: valueType.unit, outputType: valueType.watchHandle, eventType: slot.valueType, }; case "watch-stop": return { mode: "watch-stop", inputType: valueType.watchHandle, outputType: valueType.unit, }; } }; const constraintValueType = (constraint: EdgeEndpointConstraint): ValueType => constraint.kind === "atom" ? valueType.atomRef(constraint.atomId) : valueType.interfaceRef(constraint.interfaceRevisionId); export const cardinalityValueType = ( constraint: EdgeEndpointConstraint, cardinality: EdgeCardinality, ): ValueType => { const target = constraintValueType(constraint); switch (cardinality) { case "exactly-one": return target; case "optional-one": return valueType.optional(target); case "many": case "many-unique": return valueType.list(target); } }; const edgeProjection = ( edge: EdgeDefinition, projectionId: EdgeProjectionId, ) => { const index = edge.endpoints.findIndex( (endpoint) => endpoint.projectionId === projectionId, ); if (index !== 0 && index !== 1) { return undefined; } const endpointIndex = index as 0 | 1; return { endpoint: edge.endpoints[endpointIndex], target: edge.endpoints[endpointIndex === 0 ? 1 : 0], endpointIndex, }; }; export const edgePrimitiveSignature = ( edge: EdgeDefinition, projectionId: EdgeProjectionId, primitive: EdgePrimitive, ): OperationSignature | undefined => { const projection = edgeProjection(edge, projectionId); if (!projection) { return undefined; } const targetType = constraintValueType(projection.target.constraint); const resolvedType = cardinalityValueType( projection.target.constraint, projection.endpoint.cardinality, ); switch (primitive) { case "resolve": return { mode: "call", inputType: valueType.unit, outputType: resolvedType, }; case "connect": case "disconnect": return { mode: "call", inputType: targetType, outputType: valueType.unit, }; case "watch-start": return { mode: "watch-start", inputType: valueType.unit, outputType: valueType.watchHandle, eventType: resolvedType, }; case "watch-stop": return { mode: "watch-stop", inputType: valueType.watchHandle, outputType: valueType.unit, }; } }; const atomSatisfiesConstraint = ( atomId: AtomId, constraint: EdgeEndpointConstraint, conformances: ReadonlyMap, ) => constraint.kind === "atom" ? constraint.atomId === atomId : conformances.has(conformanceKey(atomId, constraint.interfaceRevisionId)); const constraintSatisfiesConstraint = ( actual: EdgeEndpointConstraint, required: EdgeEndpointConstraint, conformances: ReadonlyMap, ) => constraintsEqual(actual, required) || ( actual.kind === "atom" && required.kind === "interface" && atomSatisfiesConstraint(actual.atomId, required, conformances) ); const canAccessAttachment = ( owner: AttachmentOwner, conformance: Pick | undefined, ) => owner.kind === "workspace" || ( conformance !== undefined && owner.atomId === conformance.atomId && owner.interfaceRevisionId === conformance.interfaceRevisionId ); const collectIdentityIndexes = ( workspace: WorkspaceRevision, issues: CapabilityValidationIssue[], ): ValidationIndexes => { const atoms = new Map(); for (const [index, atom] of workspace.atoms.entries()) { const path = `atoms[${index}]`; requireText(issues, atom.id, `${path}.id`, "Atom ID"); requireText(issues, atom.displayName, `${path}.displayName`, "Atom name"); if (atoms.has(atom.id)) { issue(issues, "duplicate-atom", `${path}.id`, `Duplicate atom ${atom.id}`); } else { atoms.set(atom.id, atom); } } const interfaces = new Map(); for (const [interfaceIndex, revision] of workspace.interfaceImports.entries()) { const path = `interfaceImports[${interfaceIndex}]`; requireText(issues, revision.interfaceId, `${path}.interfaceId`, "Interface ID"); requireText( issues, revision.revisionId, `${path}.revisionId`, "Interface revision ID", ); requireText(issues, revision.displayName, `${path}.displayName`, "Interface name"); validateSource(issues, revision.source, `${path}.source`); const memberIds = new Set(); const operations = new Map(); for (const [memberIndex, member] of revision.members.entries()) { const memberPath = `${path}.members[${memberIndex}]`; requireText(issues, member.id, `${memberPath}.id`, "Member ID"); requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name"); if (memberIds.has(member.id)) { issue( issues, "duplicate-interface-member", `${memberPath}.id`, `Duplicate member ${member.id}`, ); } memberIds.add(member.id); for (const [operationIndex, operation] of member.operations.entries()) { const operationPath = `${memberPath}.operations[${operationIndex}]`; requireText(issues, operation.id, `${operationPath}.id`, "Operation ID"); requireText( issues, operation.displayName, `${operationPath}.displayName`, "Operation name", ); validateOperationEventShape(issues, operation, operationPath); if (operations.has(operation.id)) { issue( issues, "duplicate-interface-operation", `${operationPath}.id`, `Duplicate operation ${operation.id}`, ); } else { operations.set(operation.id, { operation, member }); } } } if (interfaces.has(revision.revisionId)) { issue( issues, "duplicate-interface-revision", `${path}.revisionId`, `Duplicate interface revision ${revision.revisionId}`, ); } else { interfaces.set(revision.revisionId, { revision, operations }); } } const packages = new Map(); for (const [packageIndex, revision] of workspace.packageImports.entries()) { const path = `packageImports[${packageIndex}]`; if (revision.semanticMajor !== undefined && (!Number.isSafeInteger(revision.semanticMajor) || revision.semanticMajor < 1)) { issue(issues, "invalid-semantic-major", `${path}.semanticMajor`, "Semantic major must be a positive safe integer"); } requireText(issues, revision.packageId, `${path}.packageId`, "Package ID"); requireText( issues, revision.revisionId, `${path}.revisionId`, "Package revision ID", ); requireText(issues, revision.displayName, `${path}.displayName`, "Package name"); validateSource(issues, revision.source, `${path}.source`); const exports = new Map(); for (const [exportIndex, entry] of revision.exports.entries()) { const exportPath = `${path}.exports[${exportIndex}]`; requireText(issues, entry.id, `${exportPath}.id`, "Package export ID"); requireText(issues, entry.displayName, `${exportPath}.displayName`, "Package export name"); if (exports.has(entry.id)) { issue( issues, "duplicate-package-export", `${exportPath}.id`, `Duplicate package export ${entry.id}`, ); } else { exports.set(entry.id, entry); } const portIds = new Set(); for (const [portIndex, port] of entry.dependencyPorts.entries()) { const portPath = `${exportPath}.dependencyPorts[${portIndex}]`; requireText(issues, port.id, `${portPath}.id`, "Dependency port ID"); requireText(issues, port.displayName, `${portPath}.displayName`, "Dependency port name"); if (portIds.has(port.id)) { issue( issues, "duplicate-dependency-port", `${portPath}.id`, `Duplicate dependency port ${port.id}`, ); } portIds.add(port.id); } } if (packages.has(revision.revisionId)) { issue( issues, "duplicate-package-revision", `${path}.revisionId`, `Duplicate package revision ${revision.revisionId}`, ); } else { packages.set(revision.revisionId, { revision, exports }); } } const conformances = new Map(); const conformancePaths = new Map(); const conformanceIds = new Set(); for (const [index, conformance] of workspace.conformances.entries()) { const path = `conformances[${index}]`; if (conformance.semanticMajor !== undefined && (!Number.isSafeInteger(conformance.semanticMajor) || conformance.semanticMajor < 1)) { issue(issues, "invalid-semantic-major", `${path}.semanticMajor`, "Semantic major must be a positive safe integer"); } if (conformance.id !== undefined) { requireText(issues, conformance.id, `${path}.id`, "Conformance ID"); if (conformanceIds.has(conformance.id)) issue(issues, "duplicate-conformance-id", `${path}.id`, `Duplicate conformance ID ${conformance.id}`); conformanceIds.add(conformance.id); } const key = conformanceKey(conformance.atomId, conformance.interfaceRevisionId); if (conformances.has(key)) { issue( issues, "duplicate-conformance", path, `Duplicate conformance for ${conformance.atomId} as ${conformance.interfaceRevisionId}`, ); } else { conformances.set(key, conformance); conformancePaths.set(key, path); } } const constructors = new Map(); for (const [index, constructor] of workspace.constructors.entries()) { const path = `constructors[${index}]`; if (constructors.has(constructor.atomId)) { issue( issues, "duplicate-constructor", path, `Atom ${constructor.atomId} has more than one constructor`, ); } else { constructors.set(constructor.atomId, constructor); } } const attachments = new Map(); const projections = new Map< string, { edge: EdgeDefinition; endpoint: EdgeEndpoint; endpointIndex: 0 | 1 } >(); const addAttachment = ( attachment: PersistentAttachment, owner: AttachmentOwner, path: string, ) => { const key = attachmentKey(attachment); if (attachments.has(key)) { issue( issues, "duplicate-attachment", `${path}.id`, `Duplicate ${attachment.kind} attachment ${attachment.id}`, ); } else { attachments.set(key, { attachment, owner, path }); } if (attachment.kind === "edge") { for (const [endpointIndex, endpoint] of attachment.endpoints.entries()) { const endpointPath = `${path}.endpoints[${endpointIndex}]`; if (projections.has(endpoint.projectionId)) { issue( issues, "duplicate-projection", `${endpointPath}.projectionId`, `Duplicate edge projection ${endpoint.projectionId}`, ); } else { projections.set(endpoint.projectionId, { edge: attachment, endpoint, endpointIndex: endpointIndex as 0 | 1, }); } } } }; for (const [index, attachment] of workspace.sharedAttachments.entries()) { addAttachment(attachment, { kind: "workspace" }, `sharedAttachments[${index}]`); } for (const [conformanceIndex, conformance] of workspace.conformances.entries()) { for (const [attachmentIndex, attachment] of conformance.privateAttachments.entries()) { addAttachment( attachment, { kind: "conformance", atomId: conformance.atomId, interfaceRevisionId: conformance.interfaceRevisionId, }, `conformances[${conformanceIndex}].privateAttachments[${attachmentIndex}]`, ); } } return { atoms, interfaces, packages, conformances, conformancePaths, attachments, projections, constructors, }; }; const validateInterfaces = ( workspace: WorkspaceRevision, issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { for (const [interfaceIndex, revision] of workspace.interfaceImports.entries()) { const path = `interfaceImports[${interfaceIndex}]`; for (const [memberIndex, member] of revision.members.entries()) { const memberPath = `${path}.members[${memberIndex}]`; if (member.kind === "value") { validateValueType(issues, member.valueType, `${memberPath}.valueType`, indexes); } else if (member.kind === "relationship") { validateConstraint(issues, member.target, `${memberPath}.target`, indexes); } else { validateValueType(issues, member.inputType, `${memberPath}.inputType`, indexes); validateValueType(issues, member.outputType, `${memberPath}.outputType`, indexes); } for (const [operationIndex, operation] of member.operations.entries()) { const operationPath = `${memberPath}.operations[${operationIndex}]`; validateValueType(issues, operation.inputType, `${operationPath}.inputType`, indexes); validateValueType(issues, operation.outputType, `${operationPath}.outputType`, indexes); if (operation.eventType) { validateValueType(issues, operation.eventType, `${operationPath}.eventType`, indexes); } } } } }; const validateAttachments = ( issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { for (const { attachment, owner, path } of indexes.attachments.values()) { requireText(issues, attachment.id, `${path}.id`, `${attachment.kind} attachment ID`); requireText(issues, attachment.displayName, `${path}.displayName`, `${attachment.kind} name`); if (attachment.kind === "state") { if (!indexes.atoms.has(attachment.attachedTo)) { issue( issues, "unresolved-reference", `${path}.attachedTo`, `Unknown atom ${attachment.attachedTo}`, ); } validateValueType(issues, attachment.valueType, `${path}.valueType`, indexes); const containsRpcType = (type: ValueType): boolean => type.kind === "object-ref" || type.kind === "record" || ((type.kind === "optional" || type.kind === "list") && containsRpcType(type.value)); if (containsRpcType(attachment.valueType) || (attachment.storagePolicy.kind === "crdt-document" && containsRpcType(attachment.storagePolicy.updateType))) { issue(issues, "invalid-attachment", `${path}.valueType`, "Managed object references belong in graph relationships, not ordinary state; record types are RPC-only"); } if (attachment.storagePolicy.kind === "crdt-document") { validateValueType( issues, attachment.storagePolicy.updateType, `${path}.storagePolicy.updateType`, indexes, ); } if (owner.kind === "conformance") { if (attachment.attachedTo !== owner.atomId) { issue( issues, "invalid-attachment", `${path}.attachedTo`, `Private state must attach to its conformance atom ${owner.atomId}`, ); } } continue; } if (attachment.endpoints.length !== 2) { issue( issues, "invalid-attachment", `${path}.endpoints`, `Edge ${attachment.id} must have exactly two endpoints`, ); } for (const [endpointIndex, endpoint] of attachment.endpoints.entries()) { const endpointPath = `${path}.endpoints[${endpointIndex}]`; if (endpoint.keyType !== undefined && (!["string", "boolean", "int64"].includes(endpoint.keyType) || endpoint.ordered || !["many", "many-unique"].includes(endpoint.cardinality))) { issue(issues, "invalid-attachment", `${endpointPath}.keyType`, "Keyed projections require string/boolean/int64 keys, many cardinality, and no ordering"); } requireText(issues, endpoint.projectionId, `${endpointPath}.projectionId`, "Projection ID"); if (endpoint.onDelete !== undefined && !["restrict", "detach", "cascade-other"].includes(endpoint.onDelete)) { issue(issues, "invalid-attachment", `${endpointPath}.onDelete`, "Deletion policy must be restrict, detach, or cascade-other"); } requireText(issues, endpoint.displayName, `${endpointPath}.displayName`, "Projection name"); validateConstraint(issues, endpoint.constraint, `${endpointPath}.constraint`, indexes); } if (attachment.endpoints.every((endpoint) => endpoint.keyType)) issue(issues, "invalid-attachment", `${path}.endpoints`, "A v0 map has one keyed projection and one unkeyed inverse"); if (owner.kind === "conformance") { if ( !attachment.endpoints.some((endpoint) => atomSatisfiesConstraint(owner.atomId, endpoint.constraint, indexes.conformances), ) ) { issue( issues, "invalid-attachment", `${path}.endpoints`, `Private edge has no endpoint compatible with conformance atom ${owner.atomId}`, ); } } } }; const uniquePrimitiveList = ( issues: CapabilityValidationIssue[], primitives: readonly string[], path: string, ) => { if (primitives.length === 0) { issue(issues, "invalid-dependency-binding", path, "Dependency port requires at least one primitive"); } const seen = new Set(); for (const primitive of primitives) { if (seen.has(primitive)) { issue( issues, "invalid-dependency-binding", path, `Dependency primitive ${primitive} is listed more than once`, ); } seen.add(primitive); } }; const validatePackages = ( workspace: WorkspaceRevision, issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { for (const [packageIndex, revision] of workspace.packageImports.entries()) { const path = `packageImports[${packageIndex}]`; for (const [exportIndex, entry] of revision.exports.entries()) { const exportPath = `${path}.exports[${exportIndex}]`; validateValueType(issues, entry.inputType, `${exportPath}.inputType`, indexes); validateValueType(issues, entry.outputType, `${exportPath}.outputType`, indexes); if (entry.kind === "operation") { validateOperationEventShape(issues, entry, exportPath); if (entry.eventType) { validateValueType(issues, entry.eventType, `${exportPath}.eventType`, indexes); } if (entry.receiverRequirement.kind === "exact-atom") { if (!indexes.atoms.has(entry.receiverRequirement.atomId)) { issue( issues, "unresolved-reference", `${exportPath}.receiverRequirement.atomId`, `Unknown atom ${entry.receiverRequirement.atomId}`, ); } } else if (entry.receiverRequirement.kind === "all-interfaces") { const seen = new Set(); for (const [requirementIndex, interfaceRevisionId] of entry.receiverRequirement.interfaceRevisionIds.entries()) { const requirementPath = `${exportPath}.receiverRequirement.interfaceRevisionIds[${requirementIndex}]`; if (!indexes.interfaces.has(interfaceRevisionId)) { issue( issues, "unresolved-reference", requirementPath, `Unknown interface revision ${interfaceRevisionId}`, ); } if (seen.has(interfaceRevisionId)) { issue( issues, "invalid-operation", requirementPath, `Receiver interface ${interfaceRevisionId} is duplicated`, ); } seen.add(interfaceRevisionId); } } } else if (entry.kind === "constructor") { if (!indexes.atoms.has(entry.constructsAtom)) { issue( issues, "unresolved-reference", `${exportPath}.constructsAtom`, `Unknown atom ${entry.constructsAtom}`, ); } const expected = valueType.atomRef(entry.constructsAtom); if (!valueTypesEqual(entry.outputType, expected)) { issue( issues, "invalid-constructor", `${exportPath}.outputType`, `Constructor must return ${typeLabel(expected)}, not ${typeLabel(entry.outputType)}`, ); } } for (const [portIndex, port] of entry.dependencyPorts.entries()) { const portPath = `${exportPath}.dependencyPorts[${portIndex}]`; switch (port.requirement.kind) { case "state": validateValueType(issues, port.requirement.valueType, `${portPath}.requirement.valueType`, indexes); uniquePrimitiveList(issues, port.requirement.primitives, `${portPath}.requirement.primitives`); break; case "edge": validateConstraint(issues, port.requirement.target, `${portPath}.requirement.target`, indexes); uniquePrimitiveList(issues, port.requirement.primitives, `${portPath}.requirement.primitives`); break; case "interface": if (!indexes.interfaces.has(port.requirement.interfaceRevisionId)) { issue( issues, "unresolved-reference", `${portPath}.requirement.interfaceRevisionId`, `Unknown interface revision ${port.requirement.interfaceRevisionId}`, ); } break; case "constructor": if (port.requirement.inputType) validateValueType(issues, port.requirement.inputType, `${portPath}.requirement.inputType`, indexes); if (!indexes.atoms.has(port.requirement.atomId)) { issue( issues, "unresolved-reference", `${portPath}.requirement.atomId`, `Unknown atom ${port.requirement.atomId}`, ); } } } } } }; const findAttachment = ( indexes: ValidationIndexes, kind: Kind, id: Kind extends "state" ? SlotId : EdgeTypeId, ): AttachmentIndexEntry | undefined => indexes.attachments.get(`${kind}\u0000${id}`); const validateAttachmentAccess = ( issues: CapabilityValidationIssue[], entry: AttachmentIndexEntry, conformance: Pick | undefined, path: string, ) => { if (!canAccessAttachment(entry.owner, conformance)) { issue( issues, "private-attachment-access", path, `Attachment ${entry.attachment.id} is private to conformance ${ entry.owner.kind === "conformance" ? `${entry.owner.atomId} as ${entry.owner.interfaceRevisionId}` : "" }`, ); return false; } return true; }; const validateTraversal = ( issues: CapabilityValidationIssue[], indexes: ValidationIndexes, atomId: AtomId, traversal: { edgeTypeId: EdgeTypeId; projectionId: EdgeProjectionId }, path: string, conformance?: Pick, ) => { const attachment = findAttachment(indexes, "edge", traversal.edgeTypeId); if (!attachment || attachment.attachment.kind !== "edge") { issue( issues, "invalid-dependency-binding", `${path}.edgeTypeId`, `Unknown traversal edge ${traversal.edgeTypeId}`, ); return undefined; } const projection = edgeProjection(attachment.attachment, traversal.projectionId); if (!projection) { issue( issues, "invalid-dependency-binding", `${path}.projectionId`, `Traversal projection ${traversal.projectionId} is not on edge ${traversal.edgeTypeId}`, ); return undefined; } if (!projection.endpoint.publicTraversal) validateAttachmentAccess(issues, attachment, conformance, `${path}.edgeTypeId`); if (!atomSatisfiesConstraint(atomId, projection.endpoint.constraint, indexes.conformances)) { issue( issues, "invalid-dependency-binding", `${path}.projectionId`, `Traversal projection ${traversal.projectionId} cannot originate at receiver atom ${atomId}`, ); } if (projection.endpoint.cardinality !== "exactly-one") { issue( issues, "invalid-dependency-binding", `${path}.projectionId`, "Dependency traversal must select exactly one object", ); } return projection; }; const validateBoundDependencies = ( issues: CapabilityValidationIssue[], params: { dependencies: BoundDependency[]; dependencyPorts: DependencyPort[]; atomId: AtomId; conformance?: Pick; path: string; indexes: ValidationIndexes; requirementGraph: Map>; graphSourceKey?: string; }, ) => { const ports = new Map(params.dependencyPorts.map((port) => [port.id, port])); const bindings = new Map(); for (const [index, dependency] of params.dependencies.entries()) { const path = `${params.path}.dependencies[${index}]`; if (bindings.has(dependency.portId)) { issue( issues, "invalid-dependency-binding", `${path}.portId`, `Dependency port ${dependency.portId} is bound more than once`, ); continue; } bindings.set(dependency.portId, dependency); const port = ports.get(dependency.portId); if (!port) { issue( issues, "invalid-dependency-binding", `${path}.portId`, `Unknown dependency port ${dependency.portId}`, ); continue; } const requirement = port.requirement; const binding = dependency.binding; if (requirement.kind !== binding.kind) { issue( issues, "invalid-dependency-binding", `${path}.binding`, `Port ${port.id} requires ${requirement.kind}, not ${binding.kind}`, ); continue; } switch (requirement.kind) { case "state": { const stateBinding = binding as Extract; const attachment = findAttachment(params.indexes, "state", stateBinding.slotId); if (!attachment || attachment.attachment.kind !== "state") { issue( issues, "invalid-dependency-binding", `${path}.binding.slotId`, `Unknown state attachment ${stateBinding.slotId}`, ); break; } validateAttachmentAccess(issues, attachment, params.conformance, `${path}.binding.slotId`); const traversal = stateBinding.via ? validateTraversal( issues, params.indexes, params.atomId, stateBinding.via, `${path}.binding.via`, params.conformance, ) : undefined; if ( stateBinding.via ? traversal && !atomSatisfiesConstraint( attachment.attachment.attachedTo, traversal.target.constraint, params.indexes.conformances, ) : attachment.attachment.attachedTo !== params.atomId ) { issue( issues, "invalid-dependency-binding", `${path}.binding.slotId`, stateBinding.via ? `State ${stateBinding.slotId} is not attached to the traversal target` : `State ${stateBinding.slotId} is not attached to receiver atom ${params.atomId}`, ); } if (!valueTypesEqual(requirement.valueType, attachment.attachment.valueType)) { issue( issues, "invalid-dependency-binding", `${path}.binding.slotId`, `State port expects ${typeLabel(requirement.valueType)}, not ${typeLabel(attachment.attachment.valueType)}`, ); } break; } case "edge": { const edgeBinding = binding as Extract; const attachment = findAttachment(params.indexes, "edge", edgeBinding.edgeTypeId); if (!attachment || attachment.attachment.kind !== "edge") { issue( issues, "invalid-dependency-binding", `${path}.binding.edgeTypeId`, `Unknown edge attachment ${edgeBinding.edgeTypeId}`, ); break; } validateAttachmentAccess(issues, attachment, params.conformance, `${path}.binding.edgeTypeId`); const projection = edgeProjection(attachment.attachment, edgeBinding.projectionId); if (!projection) { issue( issues, "invalid-dependency-binding", `${path}.binding.projectionId`, `Projection ${edgeBinding.projectionId} is not on edge ${edgeBinding.edgeTypeId}`, ); break; } const traversal = edgeBinding.via ? validateTraversal( issues, params.indexes, params.atomId, edgeBinding.via, `${path}.binding.via`, params.conformance, ) : undefined; const originMatches = edgeBinding.via ? traversal && ( projection.endpoint.constraint.kind === "atom" ? atomSatisfiesConstraint( projection.endpoint.constraint.atomId, traversal.target.constraint, params.indexes.conformances, ) : constraintsEqual(projection.endpoint.constraint, traversal.target.constraint) ) : atomSatisfiesConstraint( params.atomId, projection.endpoint.constraint, params.indexes.conformances, ); if (!originMatches) { issue( issues, "invalid-dependency-binding", `${path}.binding.projectionId`, edgeBinding.via ? `Projection ${edgeBinding.projectionId} cannot originate at the traversal target` : `Projection ${edgeBinding.projectionId} cannot originate at receiver atom ${params.atomId}`, ); } if ( !constraintSatisfiesConstraint( projection.target.constraint, requirement.target, params.indexes.conformances, ) || requirement.cardinality !== projection.endpoint.cardinality ) { issue( issues, "invalid-dependency-binding", `${path}.binding`, `Edge port target or cardinality does not match projection ${edgeBinding.projectionId}`, ); } break; } case "interface": { const interfaceBinding = binding as Extract< DependencyBinding, { kind: "interface" } >; if (interfaceBinding.interfaceRevisionId !== requirement.interfaceRevisionId) { issue( issues, "invalid-dependency-binding", `${path}.binding.interfaceRevisionId`, `Interface port requires ${requirement.interfaceRevisionId}`, ); break; } const traversal = interfaceBinding.via ? validateTraversal( issues, params.indexes, params.atomId, interfaceBinding.via, `${path}.binding.via`, params.conformance, ) : undefined; const candidateAtoms = interfaceBinding.via ? traversal ? [...params.indexes.atoms.keys()].filter((atomId) => atomSatisfiesConstraint( atomId as AtomId, traversal.target.constraint, params.indexes.conformances, )) as AtomId[] : [] : [params.atomId]; for (const atomId of candidateAtoms) { const targetKey = conformanceKey(atomId, requirement.interfaceRevisionId); if (!params.indexes.conformances.has(targetKey)) { issue( issues, "unsatisfied-interface", `${path}.binding.interfaceRevisionId`, `${interfaceBinding.via ? "Related" : "Receiver"} atom ${atomId} does not conform to ${requirement.interfaceRevisionId}`, ); } else if (params.graphSourceKey) { params.requirementGraph.get(params.graphSourceKey)?.add(targetKey); } } break; } case "constructor": { const constructorBinding = binding as Extract; if (constructorBinding.atomId !== requirement.atomId) { issue( issues, "invalid-dependency-binding", `${path}.binding.atomId`, `Constructor port requires atom ${requirement.atomId}`, ); } if (!params.indexes.constructors.has(requirement.atomId)) { issue( issues, "invalid-dependency-binding", `${path}.binding.atomId`, `Atom ${requirement.atomId} has no constructor binding`, ); } if (requirement.inputType) { const selected = params.indexes.constructors.get(requirement.atomId); const implementation = selected ? params.indexes.packages.get(selected.packageRevisionId)?.exports.get(selected.exportId) : undefined; if (implementation && !valueTypesEqual(requirement.inputType, implementation.inputType)) { issue(issues, "invalid-dependency-binding", `${path}.binding.atomId`, `Constructor port requires input ${typeLabel(requirement.inputType)}, but selected constructor accepts ${typeLabel(implementation.inputType)}`); } } break; } } } for (const port of params.dependencyPorts) { if (!bindings.has(port.id)) { issue( issues, "invalid-dependency-binding", `${params.path}.dependencies`, `Missing binding for dependency port ${port.id}`, ); } } }; const validatePackageReceiver = ( issues: CapabilityValidationIssue[], params: { entry: PackageOperationExport; atomId: AtomId; path: string; indexes: ValidationIndexes; requirementGraph: Map>; graphSourceKey: string; }, ) => { const receiver = params.entry.receiverRequirement; if (receiver.kind === "exact-atom" && receiver.atomId !== params.atomId) { issue( issues, "incompatible-package-receiver", params.path, `Package export requires atom ${receiver.atomId}, not ${params.atomId}`, ); } if (receiver.kind === "all-interfaces") { for (const requiredInterface of receiver.interfaceRevisionIds) { const targetKey = conformanceKey(params.atomId, requiredInterface); if (!params.indexes.conformances.has(targetKey)) { issue( issues, "unsatisfied-interface", params.path, `Atom ${params.atomId} does not conform to ${requiredInterface}`, ); } else { params.requirementGraph.get(params.graphSourceKey)?.add(targetKey); } } } }; const validateConformances = ( workspace: WorkspaceRevision, issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { const requirementGraph = new Map>(); for (const key of indexes.conformances.keys()) { requirementGraph.set(key, new Set()); } for (const [index, conformance] of workspace.conformances.entries()) { const path = `conformances[${index}]`; const key = conformanceKey(conformance.atomId, conformance.interfaceRevisionId); if (!indexes.atoms.has(conformance.atomId)) { issue( issues, "unresolved-reference", `${path}.atomId`, `Unknown atom ${conformance.atomId}`, ); } const interfaceEntry = indexes.interfaces.get(conformance.interfaceRevisionId); if (!interfaceEntry) { issue( issues, "unresolved-reference", `${path}.interfaceRevisionId`, `Unknown interface revision ${conformance.interfaceRevisionId}`, ); continue; } const bindings = new Map(); for (const [bindingIndex, entry] of conformance.operationBindings.entries()) { const bindingPath = `${path}.operationBindings[${bindingIndex}]`; if (bindings.has(entry.operationId)) { issue( issues, "duplicate-operation-binding", `${bindingPath}.operationId`, `Operation ${entry.operationId} is bound more than once`, ); continue; } bindings.set(entry.operationId, entry.binding); const operationEntry = interfaceEntry.operations.get(entry.operationId); if (!operationEntry) { issue( issues, "unknown-operation-binding", `${bindingPath}.operationId`, `Operation ${entry.operationId} does not belong to interface ${conformance.interfaceRevisionId}`, ); continue; } const operation = operationEntry.operation; const binding = entry.binding; if (binding.kind === "state") { const attachment = findAttachment(indexes, "state", binding.slotId); if (!attachment || attachment.attachment.kind !== "state") { issue( issues, "invalid-state-binding", `${bindingPath}.binding.slotId`, `Unknown state attachment ${binding.slotId}`, ); continue; } validateAttachmentAccess(issues, attachment, conformance, `${bindingPath}.binding.slotId`); if (attachment.attachment.attachedTo !== conformance.atomId) { issue( issues, "invalid-state-binding", `${bindingPath}.binding.slotId`, `State ${binding.slotId} is not attached to atom ${conformance.atomId}`, ); } if (operationEntry.member.kind !== "value") { issue( issues, "invalid-state-binding", `${bindingPath}.binding`, "State primitives may only implement value members", ); } const expected = statePrimitiveSignature(attachment.attachment, binding.primitive); if (!signaturesMatch(operation, expected)) { issue( issues, "invalid-state-binding", `${bindingPath}.binding`, `State primitive provides ${describeSignature(expected)}, but operation requires ${describeSignature(operation)}`, ); } continue; } if (binding.kind === "edge") { const attachment = findAttachment(indexes, "edge", binding.edgeTypeId); if (!attachment || attachment.attachment.kind !== "edge") { issue( issues, "invalid-edge-binding", `${bindingPath}.binding.edgeTypeId`, `Unknown edge attachment ${binding.edgeTypeId}`, ); continue; } validateAttachmentAccess(issues, attachment, conformance, `${bindingPath}.binding.edgeTypeId`); const projection = edgeProjection(attachment.attachment, binding.projectionId); const relationshipMember = operationEntry.member.kind === "relationship" ? operationEntry.member : undefined; const operationEdge = relationshipMember ? { ...attachment.attachment, endpoints: attachment.attachment.endpoints.map((endpoint, index) => index === (projection?.endpointIndex === 0 ? 1 : 0) ? { ...endpoint, constraint: relationshipMember.target } : endpoint, ) as [EdgeEndpoint, EdgeEndpoint], } : attachment.attachment; const expected = edgePrimitiveSignature( operationEdge, binding.projectionId, binding.primitive, ); if (!projection || !expected) { issue( issues, "invalid-edge-binding", `${bindingPath}.binding.projectionId`, `Projection ${binding.projectionId} is not on edge ${binding.edgeTypeId}`, ); continue; } if (!atomSatisfiesConstraint(conformance.atomId, projection.endpoint.constraint, indexes.conformances)) { issue( issues, "invalid-edge-binding", `${bindingPath}.binding.projectionId`, `Projection ${binding.projectionId} cannot originate at atom ${conformance.atomId}`, ); } if (operationEntry.member.kind !== "relationship") { issue( issues, "invalid-edge-binding", `${bindingPath}.binding`, "Edge primitives may only implement relationship members", ); } else if ( !constraintSatisfiesConstraint( projection.target.constraint, operationEntry.member.target, indexes.conformances, ) || operationEntry.member.cardinality !== projection.endpoint.cardinality ) { issue( issues, "invalid-edge-binding", `${bindingPath}.binding`, "Edge projection target or cardinality does not match relationship member", ); } if (!signaturesMatch(operation, expected)) { issue( issues, "invalid-edge-binding", `${bindingPath}.binding`, `Edge primitive provides ${describeSignature(expected)}, but operation requires ${describeSignature(operation)}`, ); } continue; } const packageEntry = indexes.packages.get(binding.packageRevisionId); const packageExport = packageEntry?.exports.get(binding.exportId); if (!packageEntry) { issue( issues, "invalid-package-binding", `${bindingPath}.binding.packageRevisionId`, `Unknown package revision ${binding.packageRevisionId}`, ); continue; } if (!packageExport) { issue( issues, "invalid-package-binding", `${bindingPath}.binding.exportId`, `Unknown export ${binding.exportId} in package ${binding.packageRevisionId}`, ); continue; } if (packageExport.kind !== "operation") { issue( issues, "invalid-package-binding", `${bindingPath}.binding.exportId`, `Package export ${binding.exportId} is ${packageExport.kind}, not an operation`, ); continue; } if (!signaturesMatch(operation, packageExport)) { issue( issues, "invalid-package-binding", `${bindingPath}.binding.exportId`, `Package export provides ${describeSignature(packageExport)}, but operation requires ${describeSignature(operation)}`, ); } validatePackageReceiver(issues, { entry: packageExport, atomId: conformance.atomId, path: `${bindingPath}.binding.exportId`, indexes, requirementGraph, graphSourceKey: key, }); validateBoundDependencies(issues, { dependencies: binding.dependencies, dependencyPorts: packageExport.dependencyPorts, atomId: conformance.atomId, conformance, path: `${bindingPath}.binding`, indexes, requirementGraph, graphSourceKey: key, }); } const materializedMembers = new Set(); for (const [materializationIndex, materialization] of (conformance.relationshipMaterializations ?? []).entries()) { const materializationPath = `${path}.relationshipMaterializations[${materializationIndex}]`; if (materializedMembers.has(materialization.memberId)) { issue( issues, "duplicate-relationship-materialization", materializationPath, `Relationship ${materialization.memberId} has more than one materialization recipe`, ); continue; } materializedMembers.add(materialization.memberId); const member = interfaceEntry.revision.members.find((entry) => entry.id === materialization.memberId); if (!member || member.kind !== "relationship") { issue( issues, "invalid-relationship-materialization", `${materializationPath}.memberId`, `Member ${materialization.memberId} is not a relationship on ${conformance.interfaceRevisionId}`, ); continue; } if (member.cardinality !== "optional-one") { issue( issues, "invalid-relationship-materialization", `${materializationPath}.memberId`, "A lazily constructed relationship must be optional-one before construction", ); } const resolveOperation = member.operations.find((entry) => entry.displayName === "resolve"); const resolveBinding = resolveOperation ? bindings.get(resolveOperation.id) : undefined; if (!resolveBinding || resolveBinding.kind !== "edge") { issue( issues, "invalid-relationship-materialization", `${materializationPath}.memberId`, "A materialized relationship must bind its resolve operation to an edge", ); continue; } if (resolveBinding.edgeTypeId !== materialization.edgeTypeId) { issue( issues, "invalid-relationship-materialization", `${materializationPath}.edgeTypeId`, "Materialization and relationship resolution must use the same edge", ); } const attachment = findAttachment(indexes, "edge", materialization.edgeTypeId); if (!attachment || attachment.attachment.kind !== "edge") { issue( issues, "invalid-relationship-materialization", `${materializationPath}.edgeTypeId`, `Unknown materialization edge ${materialization.edgeTypeId}`, ); continue; } validateAttachmentAccess( issues, attachment, conformance, `${materializationPath}.edgeTypeId`, ); const hostProjection = edgeProjection( attachment.attachment, resolveBinding.projectionId, ); const constructedProjection = edgeProjection( attachment.attachment, materialization.constructedProjectionId, ); if (!hostProjection || !constructedProjection || hostProjection.endpointIndex === constructedProjection.endpointIndex) { issue( issues, "invalid-relationship-materialization", `${materializationPath}.constructedProjectionId`, "Materialization must connect through the opposite projection of the resolved edge", ); continue; } if (!atomSatisfiesConstraint( materialization.constructorAtomId, member.target, indexes.conformances, )) { issue( issues, "invalid-relationship-materialization", `${materializationPath}.constructorAtomId`, `Constructed atom ${materialization.constructorAtomId} does not satisfy the relationship target`, ); } if (!atomSatisfiesConstraint( materialization.constructorAtomId, constructedProjection.endpoint.constraint, indexes.conformances, ) || !atomSatisfiesConstraint( conformance.atomId, constructedProjection.target.constraint, indexes.conformances, )) { issue( issues, "invalid-relationship-materialization", `${materializationPath}.constructedProjectionId`, "Constructed projection does not connect the constructed atom back to the host atom", ); } const constructor = indexes.constructors.get(materialization.constructorAtomId); const constructorExport = constructor ? indexes.packages.get(constructor.packageRevisionId)?.exports.get(constructor.exportId) : undefined; if (!constructor || !constructorExport || constructorExport.kind !== "constructor") { issue( issues, "invalid-relationship-materialization", `${materializationPath}.constructorAtomId`, `Constructed atom ${materialization.constructorAtomId} has no valid constructor`, ); } else if (!valueTypesEqual( constructorExport.inputType, valueType.atomRef(conformance.atomId), )) { issue( issues, "invalid-relationship-materialization", `${materializationPath}.constructorAtomId`, `Component constructor must accept ${typeLabel(valueType.atomRef(conformance.atomId))}`, ); } } for (const operation of interfaceEntry.operations.values()) { if (!bindings.has(operation.operation.id)) { issue( issues, "missing-operation-binding", `${path}.operationBindings`, `Missing binding for operation ${operation.operation.id}`, ); } } } const visitState = new Map(); const visit = (key: string, stack: string[]) => { const state = visitState.get(key); if (state === "visited") { return; } if (state === "visiting") { const cycleStart = stack.indexOf(key); const cycle = [...stack.slice(Math.max(0, cycleStart)), key]; issue( issues, "cyclic-conformance-requirement", indexes.conformancePaths.get(key) ?? "conformances", `Cyclic conformance requirement: ${cycle.join(" -> ")}`, ); return; } visitState.set(key, "visiting"); for (const dependency of requirementGraph.get(key) ?? []) { visit(dependency, [...stack, key]); } visitState.set(key, "visited"); }; for (const key of requirementGraph.keys()) { visit(key, []); } }; const validateConstructors = ( workspace: WorkspaceRevision, issues: CapabilityValidationIssue[], indexes: ValidationIndexes, ) => { const noGraph = new Map>(); for (const [index, constructor] of workspace.constructors.entries()) { const path = `constructors[${index}]`; if (!indexes.atoms.has(constructor.atomId)) { issue(issues, "invalid-constructor", `${path}.atomId`, `Unknown atom ${constructor.atomId}`); } const packageEntry = indexes.packages.get(constructor.packageRevisionId); const packageExport = packageEntry?.exports.get(constructor.exportId); if (!packageEntry) { issue( issues, "invalid-constructor", `${path}.packageRevisionId`, `Unknown package revision ${constructor.packageRevisionId}`, ); continue; } if (!packageExport) { issue( issues, "invalid-constructor", `${path}.exportId`, `Unknown export ${constructor.exportId}`, ); continue; } if (packageExport.kind !== "constructor") { issue( issues, "invalid-constructor", `${path}.exportId`, `Export ${constructor.exportId} is ${packageExport.kind}, not a constructor`, ); continue; } if (packageExport.constructsAtom !== constructor.atomId) { issue( issues, "invalid-constructor", `${path}.exportId`, `Export constructs ${packageExport.constructsAtom}, not ${constructor.atomId}`, ); } validateBoundDependencies(issues, { dependencies: constructor.dependencies, dependencyPorts: packageExport.dependencyPorts, atomId: constructor.atomId, path, indexes, requirementGraph: noGraph, }); } }; export const validateWorkspaceRevision = ( workspace: WorkspaceRevision, ): CapabilityValidationIssue[] => { const issues: CapabilityValidationIssue[] = []; requireText(issues, workspace.id, "id", "Workspace revision ID"); requireText(issues, workspace.workspaceId, "workspaceId", "Workspace ID"); requireText(issues, workspace.sourceRootCommit, "sourceRootCommit", "Source root commit"); const indexes = collectIdentityIndexes(workspace, issues); validateInterfaces(workspace, issues, indexes); validateAttachments(issues, indexes); validatePackages(workspace, issues, indexes); validateConformances(workspace, issues, indexes); validateConstructors(workspace, issues, indexes); return issues; }; export interface CompiledConformance { source: Conformance; operationBindings: ReadonlyMap; } export interface CompiledWorkspaceRevision { source: WorkspaceRevision; atoms: ReadonlyMap; interfaces: ReadonlyMap; packages: ReadonlyMap; attachments: ReadonlyMap; conformances: ReadonlyMap; constructors: ReadonlyMap; } export type CompileWorkspaceRevisionResult = | { ok: true; plan: CompiledWorkspaceRevision } | { ok: false; issues: CapabilityValidationIssue[] }; export const compileWorkspaceRevision = ( workspace: WorkspaceRevision, ): CompileWorkspaceRevisionResult => { const snapshot = structuredClone(workspace) as WorkspaceRevision; const issues = validateWorkspaceRevision(snapshot); if (issues.length > 0) { return { ok: false, issues }; } const attachments = new Map(); for (const attachment of snapshot.sharedAttachments) { attachments.set(attachmentKey(attachment), { attachment, owner: { kind: "workspace" }, }); } const conformances = new Map(); for (const conformance of snapshot.conformances) { for (const attachment of conformance.privateAttachments) { attachments.set(attachmentKey(attachment), { attachment, owner: { kind: "conformance", atomId: conformance.atomId, interfaceRevisionId: conformance.interfaceRevisionId, }, }); } conformances.set( conformanceKey(conformance.atomId, conformance.interfaceRevisionId), { source: conformance, operationBindings: new Map( conformance.operationBindings.map((entry) => [entry.operationId, entry.binding]), ), }, ); } return { ok: true, plan: { source: snapshot, atoms: new Map(snapshot.atoms.map((atom) => [atom.id, atom])), interfaces: new Map( snapshot.interfaceImports.map((revision) => [revision.revisionId, revision]), ), packages: new Map( snapshot.packageImports.map((revision) => [revision.revisionId, revision]), ), attachments, conformances, constructors: new Map(snapshot.constructors.map((entry) => [entry.atomId, entry])), }, }; }; export const resolveConformance = ( plan: CompiledWorkspaceRevision, atomId: AtomId, interfaceRevisionId: InterfaceRevisionId, ): CompiledConformance | undefined => plan.conformances.get(conformanceKey(atomId, interfaceRevisionId)); export const resolveOperationBinding = ( plan: CompiledWorkspaceRevision, atomId: AtomId, interfaceRevisionId: InterfaceRevisionId, operationId: OperationId, ): Binding | undefined => resolveConformance(plan, atomId, interfaceRevisionId)?.operationBindings.get( operationId, ); export type ResolvedOperationPlan = | { kind: "state"; binding: Extract; attachment: OwnedAttachment & { attachment: StateSlotDefinition }; } | { kind: "edge"; binding: Extract; attachment: OwnedAttachment & { attachment: EdgeDefinition }; } | { kind: "package"; binding: Extract; packageRevision: PackageRevision; packageExport: PackageOperationExport; dependencies: Array<{ port: DependencyPort; binding: DependencyBinding; }>; }; export const resolveOperationPlan = ( plan: CompiledWorkspaceRevision, atomId: AtomId, interfaceRevisionId: InterfaceRevisionId, operationId: OperationId, ): ResolvedOperationPlan | undefined => { const binding = resolveOperationBinding(plan, atomId, interfaceRevisionId, operationId); if (!binding) { return undefined; } if (binding.kind === "state") { const owned = plan.attachments.get(`state\u0000${binding.slotId}`); if (!owned || owned.attachment.kind !== "state") { return undefined; } return { kind: "state", binding, attachment: owned as OwnedAttachment & { attachment: StateSlotDefinition; }, }; } if (binding.kind === "edge") { const owned = plan.attachments.get(`edge\u0000${binding.edgeTypeId}`); if (!owned || owned.attachment.kind !== "edge") { return undefined; } return { kind: "edge", binding, attachment: owned as OwnedAttachment & { attachment: EdgeDefinition }, }; } const packageRevision = plan.packages.get(binding.packageRevisionId); const packageExport = packageRevision?.exports.find( (entry): entry is PackageOperationExport => entry.id === binding.exportId && entry.kind === "operation", ); if (!packageRevision || !packageExport) { return undefined; } const bound = new Map(binding.dependencies.map((entry) => [entry.portId, entry.binding])); return { kind: "package", binding, packageRevision, packageExport, dependencies: packageExport.dependencyPorts.map((port) => ({ port, binding: bound.get(port.id)!, })), }; }; export interface CapabilityClosure { conformances: Array<{ atomId: AtomId; interfaceRevisionId: InterfaceRevisionId; }>; packageRevisionIds: PackageRevisionId[]; attachmentIds: Array; constructorAtomIds: AtomId[]; } export const computeCapabilityClosure = ( plan: CompiledWorkspaceRevision, roots: Array<{ atomId: AtomId; interfaceRevisionId: InterfaceRevisionId }>, ): CapabilityClosure => { const conformances = new Map(); const packages = new Set(); const attachments = new Set(); const constructors = new Set(); const queued = [...roots]; const visited = new Set(); const sourceConformances = new Map( [...plan.conformances].map(([key, value]) => [key, value.source]), ); const includeDependencies = ( atomId: AtomId, dependencies: readonly BoundDependency[], ) => { for (const dependency of dependencies) { switch (dependency.binding.kind) { case "state": attachments.add(dependency.binding.slotId); if (dependency.binding.via) { attachments.add(dependency.binding.via.edgeTypeId); } break; case "edge": attachments.add(dependency.binding.edgeTypeId); if (dependency.binding.via) { attachments.add(dependency.binding.via.edgeTypeId); } break; case "interface": { if (dependency.binding.via) { attachments.add(dependency.binding.via.edgeTypeId); } const targetAtoms = dependency.binding.via ? (() => { const attachment = plan.attachments.get( `edge\u0000${dependency.binding.via!.edgeTypeId}`, )?.attachment; if (!attachment || attachment.kind !== "edge") return []; const projection = edgeProjection( attachment, dependency.binding.via!.projectionId, ); return projection ? [...plan.atoms.keys()].filter((candidate) => atomSatisfiesConstraint( candidate, projection.target.constraint, sourceConformances, )) : []; })() : [atomId]; for (const targetAtom of targetAtoms) { queued.push({ atomId: targetAtom, interfaceRevisionId: dependency.binding.interfaceRevisionId, }); } break; } case "constructor": { constructors.add(dependency.binding.atomId); const constructor = plan.constructors.get(dependency.binding.atomId); if (constructor) { packages.add(constructor.packageRevisionId); includeDependencies(dependency.binding.atomId, constructor.dependencies); } } } } }; while (queued.length > 0) { const root = queued.shift()!; const key = conformanceKey(root.atomId, root.interfaceRevisionId); if (visited.has(key)) { continue; } visited.add(key); const conformance = plan.conformances.get(key); if (!conformance) { continue; } conformances.set(key, { atomId: conformance.source.atomId, interfaceRevisionId: conformance.source.interfaceRevisionId, }); for (const binding of conformance.operationBindings.values()) { if (binding.kind === "state") { attachments.add(binding.slotId); } else if (binding.kind === "edge") { attachments.add(binding.edgeTypeId); } else { packages.add(binding.packageRevisionId); includeDependencies(root.atomId, binding.dependencies); const packageRevision = plan.packages.get(binding.packageRevisionId); const operation = packageRevision?.exports.find( (entry): entry is PackageOperationExport => entry.id === binding.exportId && entry.kind === "operation", ); if (operation?.receiverRequirement.kind === "all-interfaces") { for (const interfaceRevisionId of operation.receiverRequirement .interfaceRevisionIds) { queued.push({ atomId: root.atomId, interfaceRevisionId }); } } } } for (const materialization of conformance.source.relationshipMaterializations ?? []) { attachments.add(materialization.edgeTypeId); constructors.add(materialization.constructorAtomId); const constructor = plan.constructors.get(materialization.constructorAtomId); if (constructor) { packages.add(constructor.packageRevisionId); includeDependencies(materialization.constructorAtomId, constructor.dependencies); } } } return { conformances: [...conformances.entries()] .sort(([left], [right]) => left.localeCompare(right)) .map(([, conformance]) => conformance), packageRevisionIds: [...packages].sort(), attachmentIds: [...attachments].sort(), constructorAtomIds: [...constructors].sort(), }; };