2026-09-20 22:57:27 -07:00
import { isStorableType } from "./generics.js" ;
2026-09-28 17:20:35 -07:00
import {
journalPayloadType ,
journalPrimitiveSignature ,
subscriptionContract ,
subscriptionStatusType ,
} from "./journals.js" ;
2026-09-19 22:09:55 -07:00
import { validateInitialization } from "./initialization.js" ;
2026-09-21 11:23:26 -07:00
import { validatePresentations } from "./presentations.js" ;
2026-09-22 13:16:23 -07:00
import { validateModules } from "./modules.js" ;
2026-09-21 11:23:26 -07:00
import { validateWorkspaceControllers } from "./controllers.js" ;
2026-09-19 21:52:37 -07:00
import { validateExecutionContracts } from "./execution.js" ;
2026-09-18 16:59:19 -07:00
import { resolveForwardedBinding , type ConcreteBinding } from "./forwarding.js" ;
2026-09-03 15:46:52 -07:00
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 ,
2026-09-16 11:25:20 -07:00
PackageFunctionExport ,
2026-09-03 15:46:52 -07:00
PackageRevision ,
PackageRevisionId ,
PersistentAttachment ,
SlotId ,
SourceRevision ,
StatePrimitive ,
StateSlotDefinition ,
ValueType ,
WorkspaceRevision ,
} from "./types.js" ;
import { valueType } from "./types.js" ;
2026-09-16 00:12:39 -07:00
import { appliedInterfaceId , GenericTypeError } from "./generics.js" ;
import { specializePackageExport } from "./generic-packages.js" ;
import { isDeepStrictEqual } from "node:util" ;
2026-09-03 15:46:52 -07:00
export type CapabilityValidationIssueCode =
2026-09-21 11:23:26 -07:00
| "invalid-controller"
| "invalid-presentation"
2026-09-22 13:16:23 -07:00
| "invalid-module-contract"
2026-09-19 22:09:55 -07:00
| "invalid-initialization"
2026-09-19 21:52:37 -07:00
| "invalid-execution-contract"
2026-09-17 14:34:16 -07:00
| "invalid-query-contract"
| "query-provider-reason-required"
2026-09-10 18:27:41 -07:00
| "invalid-semantic-major"
| "duplicate-conformance-id"
2026-09-03 15:46:52 -07:00
| "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"
2026-09-04 21:03:09 -07:00
| "duplicate-relationship-materialization"
2026-09-03 15:46:52 -07:00
| "duplicate-constructor"
| "unresolved-reference"
| "invalid-attachment"
| "invalid-state-binding"
| "invalid-edge-binding"
2026-09-20 22:41:51 -07:00
| "invalid-lifecycle-binding"
2026-09-03 15:46:52 -07:00
| "invalid-package-binding"
2026-09-18 16:59:19 -07:00
| "invalid-forwarding-binding"
2026-09-03 15:46:52 -07:00
| "invalid-dependency-binding"
| "private-attachment-access"
| "incompatible-package-receiver"
2026-09-05 12:33:52 -07:00
| "unsatisfied-interface"
2026-09-03 15:46:52 -07:00
| "cyclic-conformance-requirement"
| "missing-operation-binding"
| "unknown-operation-binding"
2026-09-04 21:03:09 -07:00
| "invalid-constructor"
| "invalid-relationship-materialization" ;
2026-09-03 15:46:52 -07:00
export interface CapabilityValidationIssue {
code : CapabilityValidationIssueCode ;
path : string ;
message : string ;
}
interface InterfaceOperationEntry {
operation : InterfaceOperation ;
member : InterfaceMember ;
}
interface InterfaceIndexEntry {
revision : InterfaceRevision ;
operations : Map < OperationId , InterfaceOperationEntry >;
}
interface PackageIndexEntry {
revision : PackageRevision ;
exports : Map < string , PackageExport >;
}
interface AttachmentIndexEntry extends OwnedAttachment {
path : string ;
}
interface ValidationIndexes {
atoms : Map < string , WorkspaceRevision [ "atoms" ][ number ] >;
interfaces : Map < string , InterfaceIndexEntry >;
packages : Map < string , PackageIndexEntry >;
conformances : Map < string , Conformance >;
conformancePaths : Map < string , string >;
attachments : Map < string , AttachmentIndexEntry >;
2026-09-15 15:23:24 -07:00
projections : Map < string , { edge : EdgeDefinition ; endpoint : EdgeEndpoint ; endpointIndex : 0 | 1 }>;
2026-09-03 15:46:52 -07:00
constructors : Map < AtomId , AtomConstructorBinding >;
}
const hasText = ( value : string ) => value . trim (). length > 0 ;
2026-09-15 15:23:24 -07:00
const conformanceKey = ( atomId : AtomId , interfaceRevisionId : InterfaceRevisionId ) : string =>
` ${ atomId } \ u0000 ${ interfaceRevisionId } ` ;
2026-09-03 15:46:52 -07:00
2026-09-15 15:23:24 -07:00
const attachmentKey = ( attachment : PersistentAttachment ) : string => ` ${ attachment . kind } \ u0000 ${ attachment . id } ` ;
2026-09-03 15:46:52 -07:00
const issue = (
issues : CapabilityValidationIssue [],
code : CapabilityValidationIssueCode ,
path : string ,
message : string ,
) => issues . push ({ code , path , message });
2026-09-15 15:23:24 -07:00
const requireText = ( issues : CapabilityValidationIssue [], value : string , path : string , label : string ) => {
2026-09-03 15:46:52 -07:00
if ( ! hasText ( value )) {
issue ( issues , "required-value" , path , ` ${ label } is required` );
}
};
const typeLabel = ( type : ValueType ) : string => {
switch ( type . kind ) {
2026-09-27 22:06:12 -07:00
case "variant" :
return `variant: ${ type . id } { ${ Object . entries ( type . cases )
. sort (([ a ], [ b ]) => a . localeCompare ( b ))
. map (([ label , entry ]) => ` ${ label } : ${ entry . id } : ${ typeLabel ( entry . value ) } ` )
. join ( ";" ) } }` ;
2026-09-03 15:46:52 -07:00
case "builtin" :
case "scalar" :
return type . name ;
case "message" :
return `message: ${ type . descriptorId } ` ;
2026-09-13 16:30:17 -07:00
case "record" :
2026-09-15 15:23:24 -07:00
return `record{ ${ Object . keys ( type . fields )
. sort ()
. map (( key ) => ` ${ key } : ${ typeLabel ( type . fields [ key ]) } ` )
. join ( ";" ) } }` ;
2026-09-03 15:46:52 -07:00
case "object-ref" :
return type . expectation . kind === "atom"
? `object:atom: ${ type . expectation . atomId } `
: `object:interface: ${ type . expectation . interfaceRevisionId } ` ;
2026-09-19 22:20:55 -07:00
case "change" :
return `change< ${ typeLabel ( type . value ) } >` ;
2026-09-03 15:46:52 -07:00
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 ) {
2026-09-27 22:06:12 -07:00
case "variant" : {
const other = right as typeof left ;
return (
left . id === other . id &&
Object . keys ( left . cases ). length === Object . keys ( other . cases ). length &&
Object . entries ( left . cases ). every (
([ label , entry ]) =>
Object . hasOwn ( other . cases , label ) &&
entry . id === other . cases [ label ]. id &&
valueTypesEqual ( entry . value , other . cases [ label ]. value ),
)
);
}
2026-09-03 15:46:52 -07:00
case "builtin" :
case "scalar" :
return left . name === ( right as typeof left ). name ;
case "message" :
return left . descriptorId === ( right as typeof left ). descriptorId ;
2026-09-13 16:30:17 -07:00
case "record" : {
const other = ( right as typeof left ). fields ;
2026-09-15 15:23:24 -07:00
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 ]),
)
);
2026-09-13 16:30:17 -07:00
}
2026-09-03 15:46:52 -07:00
case "object-ref" : {
const other = ( right as typeof left ). expectation ;
if ( left . expectation . kind !== other . kind ) {
return false ;
}
return left . expectation . kind === "atom"
2026-09-15 15:23:24 -07:00
? left . expectation . atomId === ( other as typeof left . expectation ). atomId
: left.expectation.interfaceRevisionId === ( other as typeof left . expectation ). interfaceRevisionId ;
2026-09-03 15:46:52 -07:00
}
2026-09-19 22:20:55 -07:00
case "change" :
2026-09-03 15:46:52 -07:00
case "optional" :
case "list" :
return valueTypesEqual ( left . value , ( right as typeof left ). value );
}
};
2026-09-15 15:23:24 -07:00
const constraintsEqual = ( left : EdgeEndpointConstraint , right : EdgeEndpointConstraint ) =>
2026-09-03 15:46:52 -07:00
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" ],
];
2026-09-15 15:23:24 -07:00
const validateSource = ( issues : CapabilityValidationIssue [], source : SourceRevision , path : string ) => {
2026-09-03 15:46:52 -07:00
for ( const [ key , label ] of sourceRequiredFields ) {
if ( ! hasText ( source [ key ])) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-source" , ` ${ path } . ${ key } ` , `Source ${ label } is required` );
2026-09-03 15:46:52 -07:00
}
}
};
const validateValueType = (
issues : CapabilityValidationIssue [],
type : ValueType ,
path : string ,
indexes : Pick < ValidationIndexes , "atoms" | "interfaces" >,
) => {
switch ( type . kind ) {
2026-09-27 22:06:12 -07:00
case "variant" : {
requireText ( issues , type . id , ` ${ path } .id` , "Variant identity" );
const entries = Object . entries ( type . cases ),
ids = new Set < string >();
if ( ! entries . length || entries . length > 256 ) issue ( issues , "invalid-value-type" , path , "Use 1– 256 variant cases" );
for ( const [ label , entry ] of entries ) {
if ( ! /^[A-Za-z_][A-Za-z0-9_]*$/ . test ( label )) issue ( issues , "invalid-value-type" , path , "Invalid variant label" );
requireText ( issues , entry . id , ` ${ path } . ${ label } .id` , "Case identity" );
if ( ids . has ( entry . id )) issue ( issues , "invalid-value-type" , path , "Duplicate case identity" );
ids . add ( entry . id );
validateValueType ( issues , entry . value , ` ${ path } . ${ label } ` , indexes );
}
return ;
}
2026-09-13 16:30:17 -07:00
case "record" :
for ( const [ name , field ] of Object . entries ( type . fields )) {
2026-09-15 15:23:24 -07:00
if ( ! /^[A-Za-z_][A-Za-z0-9_]*$/ . test ( name ))
issue ( issues , "invalid-value-type" , path , "Invalid record field name" );
2026-09-13 16:30:17 -07:00
validateValueType ( issues , field , ` ${ path } .fields. ${ name } ` , indexes );
}
return ;
2026-09-03 15:46:52 -07:00
case "builtin" :
2026-09-16 00:12:39 -07:00
if ( ! [ "unit" , "watch-handle" ]. includes ( type . name ))
issue ( issues , "invalid-value-type" , path , "Unknown builtin value type" );
return ;
2026-09-03 15:46:52 -07:00
case "scalar" :
2026-09-16 00:12:39 -07:00
if ( ! [ "bool" , "bytes" , "double" , "int32" , "int64" , "string" , "uint32" , "uint64" ]. includes ( type . name ))
issue ( issues , "invalid-value-type" , path , "Unknown scalar value type" );
2026-09-03 15:46:52 -07:00
return ;
case "message" :
2026-09-15 15:23:24 -07:00
requireText ( issues , type . descriptorId , ` ${ path } .descriptorId` , "Message descriptor identity" );
2026-09-03 15:46:52 -07:00
return ;
2026-09-19 22:20:55 -07:00
case "change" :
2026-09-20 22:57:27 -07:00
if ( ! isStorableType ( type . value ))
issue (
issues ,
"invalid-value-type" ,
path ,
"change<T> requires a storable materialized type, not references or nested changes" ,
);
validateValueType ( issues , type . value , path + ".value" , indexes );
return ;
2026-09-03 15:46:52 -07:00
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 } ` ,
);
}
2026-09-15 15:23:24 -07:00
} else if ( ! indexes . interfaces . has ( type . expectation . interfaceRevisionId )) {
2026-09-03 15:46:52 -07:00
issue (
issues ,
"unresolved-reference" ,
` ${ path } .expectation.interfaceRevisionId` ,
`Unknown interface revision ${ type . expectation . interfaceRevisionId } ` ,
);
}
2026-09-16 00:12:39 -07:00
return ;
default :
issue (
issues ,
"invalid-value-type" ,
path ,
"Installed contracts require closed value types; unresolved authoring expressions are not executable" ,
);
2026-09-03 15:46:52 -07:00
}
};
const validateConstraint = (
issues : CapabilityValidationIssue [],
constraint : EdgeEndpointConstraint ,
path : string ,
indexes : Pick < ValidationIndexes , "atoms" | "interfaces" >,
) => {
if ( constraint . kind === "atom" ) {
if ( ! indexes . atoms . has ( constraint . atomId )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "unresolved-reference" , ` ${ path } .atomId` , `Unknown atom ${ constraint . atomId } ` );
2026-09-03 15:46:52 -07:00
}
} else if ( ! indexes . interfaces . has ( constraint . interfaceRevisionId )) {
issue (
issues ,
"unresolved-reference" ,
` ${ path } .interfaceRevisionId` ,
`Unknown interface revision ${ constraint . interfaceRevisionId } ` ,
);
}
};
2026-09-15 15:23:24 -07:00
const emitsEvents = ( mode : InterfaceOperationMode ) => mode === "watch-start" || mode === "subscribe" ;
2026-09-03 15:46:52 -07:00
const validateOperationEventShape = (
issues : CapabilityValidationIssue [],
operation : Pick < InterfaceOperation , "mode" | "eventType" >,
path : string ,
) => {
if ( emitsEvents ( operation . mode ) && operation . eventType === undefined ) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-operation" , ` ${ path } .eventType` , ` ${ operation . mode } operations require an event type` );
2026-09-03 15:46:52 -07:00
}
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 ;
}
2026-09-15 15:23:24 -07:00
const signaturesMatch = ( operation : OperationSignature , implementation : OperationSignature ) =>
2026-09-03 15:46:52 -07:00
operation . mode === implementation . mode &&
valueTypesEqual ( operation . inputType , implementation . inputType ) &&
valueTypesEqual ( operation . outputType , implementation . outputType ) &&
( operation . eventType === undefined
? implementation . eventType === undefined
2026-09-15 15:23:24 -07:00
: implementation . eventType !== undefined && valueTypesEqual ( operation . eventType , implementation . eventType ));
2026-09-03 15:46:52 -07:00
const describeSignature = ( signature : OperationSignature ) => {
2026-09-15 15:23:24 -07:00
const event = signature . eventType ? ` emits ${ typeLabel ( signature . eventType ) } ` : "" ;
2026-09-03 15:46:52 -07:00
return ` ${ signature . mode } ${ typeLabel ( signature . inputType ) } -> ${ typeLabel ( signature . outputType ) }${ event } ` ;
};
2026-09-15 15:23:24 -07:00
export const statePrimitiveSignature = ( slot : StateSlotDefinition , primitive : StatePrimitive ) : OperationSignature => {
const writeType = slot . storagePolicy . kind === "crdt-document" ? slot.storagePolicy.updateType : slot.valueType ;
2026-09-03 15:46:52 -07:00
switch ( primitive ) {
case "read" :
return {
mode : "call" ,
inputType : valueType.unit ,
outputType : slot.valueType ,
};
2026-09-19 22:20:55 -07:00
case "edit" :
return { mode : "call" , inputType : { kind : "change" , value : slot.valueType }, outputType : valueType.unit };
2026-09-03 15:46:52 -07:00
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 );
2026-09-15 15:23:24 -07:00
export const cardinalityValueType = ( constraint : EdgeEndpointConstraint , cardinality : EdgeCardinality ) : ValueType => {
2026-09-03 15:46:52 -07:00
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 );
}
};
2026-09-15 15:23:24 -07:00
const edgeProjection = ( edge : EdgeDefinition , projectionId : EdgeProjectionId ) => {
const index = edge . endpoints . findIndex (( endpoint ) => endpoint . projectionId === projectionId );
2026-09-03 15:46:52 -07:00
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 );
2026-09-15 15:23:24 -07:00
const resolvedType = cardinalityValueType ( projection . target . constraint , projection . endpoint . cardinality );
2026-09-03 15:46:52 -07:00
switch ( primitive ) {
case "resolve" :
return {
mode : "call" ,
inputType : valueType.unit ,
outputType : resolvedType ,
};
case "connect" :
case "disconnect" :
return {
mode : "call" ,
2026-09-25 11:37:27 -07:00
inputType :
primitive === "connect" && projection . endpoint . keyType
? {
kind : "record" ,
fields : {
mapKey : {
kind : "scalar" ,
name : projection.endpoint.keyType === "boolean" ? "bool" : projection . endpoint . keyType ,
},
target : targetType ,
},
}
: targetType ,
2026-09-03 15:46:52 -07:00
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 < string , Conformance >,
) =>
constraint . kind === "atom"
? constraint . atomId === atomId
: conformances.has ( conformanceKey ( atomId , constraint . interfaceRevisionId ));
2026-09-28 17:20:35 -07:00
const interfaceImplies = (
indexes : ValidationIndexes ,
actual : InterfaceRevisionId ,
required : InterfaceRevisionId ,
visited = new Set < string >(),
) : boolean => {
if ( actual === required ) return true ;
if ( visited . has ( actual )) return false ;
visited . add ( actual );
return ( indexes . interfaces . get ( actual ) ? . revision . requiredInterfaces ?? []). some (( entry ) =>
interfaceImplies ( indexes , entry , required , visited ),
);
};
2026-09-04 21:03:09 -07:00
const constraintSatisfiesConstraint = (
actual : EdgeEndpointConstraint ,
required : EdgeEndpointConstraint ,
2026-09-28 17:20:35 -07:00
indexes : ValidationIndexes ,
2026-09-15 15:23:24 -07:00
) =>
constraintsEqual ( actual , required ) ||
( actual . kind === "atom" &&
required . kind === "interface" &&
2026-09-28 17:20:35 -07:00
atomSatisfiesConstraint ( actual . atomId , required , indexes . conformances )) ||
( actual . kind === "interface" &&
required . kind === "interface" &&
interfaceImplies ( indexes , actual . interfaceRevisionId , required . interfaceRevisionId ));
2026-09-04 21:03:09 -07:00
2026-09-03 15:46:52 -07:00
const canAccessAttachment = (
owner : AttachmentOwner ,
conformance : Pick < Conformance , "atomId" | "interfaceRevisionId" > | undefined ,
) =>
2026-09-15 15:23:24 -07:00
owner . kind === "workspace" ||
( conformance !== undefined &&
2026-09-03 15:46:52 -07:00
owner . atomId === conformance . atomId &&
2026-09-15 15:23:24 -07:00
owner . interfaceRevisionId === conformance . interfaceRevisionId );
2026-09-03 15:46:52 -07:00
const collectIdentityIndexes = (
workspace : WorkspaceRevision ,
issues : CapabilityValidationIssue [],
) : ValidationIndexes => {
const atoms = new Map < string , WorkspaceRevision [ "atoms" ][ number ] >();
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 < string , InterfaceIndexEntry >();
for ( const [ interfaceIndex , revision ] of workspace . interfaceImports . entries ()) {
const path = `interfaceImports[ ${ interfaceIndex } ]` ;
requireText ( issues , revision . interfaceId , ` ${ path } .interfaceId` , "Interface ID" );
2026-09-16 00:12:39 -07:00
if ( revision . template )
issue ( issues , "invalid-value-type" , path , "Unapplied generic interface cannot enter an installed workspace" );
2026-09-15 15:23:24 -07:00
requireText ( issues , revision . revisionId , ` ${ path } .revisionId` , "Interface revision ID" );
2026-09-03 15:46:52 -07:00
requireText ( issues , revision . displayName , ` ${ path } .displayName` , "Interface name" );
validateSource ( issues , revision . source , ` ${ path } .source` );
2026-09-16 00:12:39 -07:00
if ( revision . application ) {
try {
if (
appliedInterfaceId ( revision . application ) !== revision . revisionId ||
revision . application . source . repository !== revision . source . repository ||
revision . application . source . commit !== revision . source . commit
)
issue (
issues ,
"invalid-value-type" ,
path ,
"Applied interface identity does not match its exact provenance and arguments" ,
);
} catch ( error ) {
if ( ! ( error instanceof GenericTypeError )) throw error ;
issue ( issues , "invalid-value-type" , path , error . message );
}
}
2026-09-03 15:46:52 -07:00
const memberIds = new Set < string >();
const operations = new Map < OperationId , InterfaceOperationEntry >();
for ( const [ memberIndex , member ] of revision . members . entries ()) {
const memberPath = ` ${ path } .members[ ${ memberIndex } ]` ;
2026-09-17 14:34:16 -07:00
if (
member . kind === "relationship" &&
member . keyType !== undefined &&
( ! [ "string" , "boolean" , "int64" ]. includes ( member . keyType ) ||
! [ "many" , "many-unique" ]. includes ( member . cardinality ))
)
issue (
issues ,
"invalid-query-contract" ,
memberPath ,
"Keyed relationships require many cardinality and string, boolean or int64 keys" ,
);
2026-09-22 13:16:23 -07:00
if ( member . kind !== "operation" && member . kind !== "module" && member . queryRead ) {
2026-09-17 14:34:16 -07:00
const contract = member . queryRead . execution ;
const getter = member . kind === "value" ? "get" : "resolve" ;
2026-09-19 21:52:37 -07:00
if ( contract !== "native" && contract !== "rpc-permitted" && contract !== "portable" )
2026-09-17 14:34:16 -07:00
issue ( issues , "invalid-query-contract" , memberPath , "Unknown query execution contract" );
if ( member . operations . filter (( operation ) => operation . displayName === getter ). length !== 1 )
issue ( issues , "invalid-query-contract" , memberPath , `Queryable members require exactly one ${ getter } ` );
if ( member . kind === "relationship" && contract === "rpc-permitted" )
issue ( issues , "invalid-query-contract" , memberPath , "RPC query relationships are not supported" );
if ( member . kind === "value" ) {
2026-09-19 22:20:55 -07:00
const queryValue = ( type : ValueType ) : boolean =>
type . kind === "scalar" ||
2026-09-27 22:06:12 -07:00
( type . kind === "variant" && isStorableType ( type )) ||
2026-09-19 22:20:55 -07:00
(( type . kind === "optional" || type . kind === "list" ) && queryValue ( type . value )) ||
( type . kind === "record" &&
Object . keys ( type . fields ). length > 0 &&
Object . values ( type . fields ). every ( queryValue ));
if ( ! queryValue ( member . valueType ))
issue (
issues ,
"invalid-query-contract" ,
memberPath ,
"Queryable values require scalar leaves in optional, list, or nonempty record shapes" ,
);
2026-09-17 14:34:16 -07:00
}
}
2026-09-28 10:28:49 -07:00
if ( member . kind === "value" && member . nativeField ) {
const names = member . operations . map (( o ) => o . displayName );
if (
! isStorableType ( member . valueType ) ||
! names . includes ( "get" ) ||
names . some (( n ) => ! [ "get" , "set" , "edit" ]. includes ( n )) ||
( names . includes ( "set" ) && names . includes ( "edit" ))
)
issue (
issues ,
"invalid-state-binding" ,
memberPath ,
"Fields require a storable value, a native getter and at most one setter or editor" ,
);
if ( member . queryRead && member . queryRead . execution !== "native" )
issue ( issues , "invalid-query-contract" , memberPath , "Fields cannot have package-backed query reads" );
}
2026-09-03 15:46:52 -07:00
requireText ( issues , member . id , ` ${ memberPath } .id` , "Member ID" );
requireText ( issues , member . displayName , ` ${ memberPath } .displayName` , "Member name" );
if ( memberIds . has ( member . id )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-interface-member" , ` ${ memberPath } .id` , `Duplicate member ${ member . id } ` );
2026-09-03 15:46:52 -07:00
}
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" );
2026-09-15 15:23:24 -07:00
requireText ( issues , operation . displayName , ` ${ operationPath } .displayName` , "Operation name" );
2026-09-03 15:46:52 -07:00
validateOperationEventShape ( issues , operation , operationPath );
if ( operations . has ( operation . id )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-interface-operation" , ` ${ operationPath } .id` , `Duplicate operation ${ operation . id } ` );
2026-09-03 15:46:52 -07:00
} 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 < string , PackageIndexEntry >();
for ( const [ packageIndex , revision ] of workspace . packageImports . entries ()) {
const path = `packageImports[ ${ packageIndex } ]` ;
2026-09-15 15:23:24 -07:00
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" ,
);
2026-09-10 18:27:41 -07:00
}
2026-09-03 15:46:52 -07:00
requireText ( issues , revision . packageId , ` ${ path } .packageId` , "Package ID" );
2026-09-15 15:23:24 -07:00
requireText ( issues , revision . revisionId , ` ${ path } .revisionId` , "Package revision ID" );
2026-09-03 15:46:52 -07:00
requireText ( issues , revision . displayName , ` ${ path } .displayName` , "Package name" );
validateSource ( issues , revision . source , ` ${ path } .source` );
const exports = new Map < string , PackageExport >();
for ( const [ exportIndex , entry ] of revision . exports . entries ()) {
const exportPath = ` ${ path } .exports[ ${ exportIndex } ]` ;
2026-09-16 00:12:39 -07:00
if ( entry . application ) {
try {
const definition = revision . genericExports ? . find (( candidate ) => candidate . id === entry . application ! . exportId );
if ( ! definition ) throw new Error ( "Missing generic export definition" );
const expected = specializePackageExport ( revision , definition , entry . application . arguments , {
arguments : new Map (),
self : entry.application.self ,
applyInterface : ( id , args ) => {
const applied = workspace . interfaceImports . find (
( contract ) =>
contract . application ? . definitionId === id && isDeepStrictEqual ( contract . application . arguments , args ),
);
if ( applied ) return applied . revisionId ;
const concrete = workspace . interfaceImports . find (( contract ) => contract . revisionId === id );
if ( concrete && ! concrete . template && args . length === 0 ) return id ;
throw new Error ( `Missing closed interface application ${ id } ` );
},
// Retained obligations are discharged against candidate conformances below.
implementsInterface : ( target , required ) =>
( revision . argumentRequirements ?? []). some (
( obligation ) => obligation . required === required && isDeepStrictEqual ( obligation . target , target ),
),
});
if ( ! isDeepStrictEqual ( entry , expected ))
throw new Error ( "Specialized export differs from its definition, source or arguments" );
} catch ( error ) {
issue ( issues , "invalid-operation" , exportPath , error instanceof Error ? error.message : String ( error ));
}
}
2026-09-03 15:46:52 -07:00
requireText ( issues , entry . id , ` ${ exportPath } .id` , "Package export ID" );
requireText ( issues , entry . displayName , ` ${ exportPath } .displayName` , "Package export name" );
if ( exports . has ( entry . id )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-package-export" , ` ${ exportPath } .id` , `Duplicate package export ${ entry . id } ` );
2026-09-03 15:46:52 -07:00
} else {
exports . set ( entry . id , entry );
}
const portIds = new Set < string >();
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 )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-dependency-port" , ` ${ portPath } .id` , `Duplicate dependency port ${ port . id } ` );
2026-09-03 15:46:52 -07:00
}
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 < string , Conformance >();
const conformancePaths = new Map < string , string >();
2026-09-10 18:27:41 -07:00
const conformanceIds = new Set < string >();
2026-09-03 15:46:52 -07:00
for ( const [ index , conformance ] of workspace . conformances . entries ()) {
const path = `conformances[ ${ index } ]` ;
2026-09-15 15:23:24 -07:00
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" ,
);
2026-09-10 18:27:41 -07:00
}
if ( conformance . id !== undefined ) {
requireText ( issues , conformance . id , ` ${ path } .id` , "Conformance ID" );
2026-09-15 15:23:24 -07:00
if ( conformanceIds . has ( conformance . id ))
issue ( issues , "duplicate-conformance-id" , ` ${ path } .id` , `Duplicate conformance ID ${ conformance . id } ` );
2026-09-10 18:27:41 -07:00
conformanceIds . add ( conformance . id );
}
2026-09-03 15:46:52 -07:00
const key = conformanceKey ( conformance . atomId , conformance . interfaceRevisionId );
if ( conformances . has ( key )) {
issue (
issues ,
"duplicate-conformance" ,
path ,
2026-09-23 00:01:03 -07:00
`Duplicate conformance for ${ conformance . atomId } as ${ conformance . interfaceRevisionId } : ` +
[ conformances . get ( key ) ! , conformance ]
. map (( candidate ) =>
candidate . ruleOrigin
? `rule ${ candidate . ruleOrigin . ruleId } from ${ candidate . ruleOrigin . sourceInterfaceRevisionId } at line ${ candidate . ruleOrigin . line } `
: `conformance ${ candidate . id ?? "(unnamed)" } ` ,
)
. join ( " conflicts with " ),
2026-09-03 15:46:52 -07:00
);
} else {
conformances . set ( key , conformance );
conformancePaths . set ( key , path );
}
}
const constructors = new Map < AtomId , AtomConstructorBinding >();
for ( const [ index , constructor ] of workspace . constructors . entries ()) {
const path = `constructors[ ${ index } ]` ;
if ( constructors . has ( constructor . atomId )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-constructor" , path , `Atom ${ constructor . atomId } has more than one constructor` );
2026-09-03 15:46:52 -07:00
} else {
constructors . set ( constructor . atomId , constructor );
}
}
const attachments = new Map < string , AttachmentIndexEntry >();
2026-09-15 15:23:24 -07:00
const projections = new Map < string , { edge : EdgeDefinition ; endpoint : EdgeEndpoint ; endpointIndex : 0 | 1 }>();
const addAttachment = ( attachment : PersistentAttachment , owner : AttachmentOwner , path : string ) => {
2026-09-03 15:46:52 -07:00
const key = attachmentKey ( attachment );
if ( attachments . has ( key )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "duplicate-attachment" , ` ${ path } .id` , `Duplicate ${ attachment . kind } attachment ${ attachment . id } ` );
2026-09-03 15:46:52 -07:00
} 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 ,
) => {
2026-09-16 00:12:39 -07:00
for ( const [ packageIndex , pkg ] of workspace . packageImports . entries ()) {
for ( const obligation of pkg . argumentRequirements ?? []) {
const satisfied =
obligation . target . kind === "atom"
? indexes . conformances . has ( conformanceKey ( obligation . target . atomId , obligation . required ))
2026-09-28 17:20:35 -07:00
: interfaceImplies ( indexes , obligation . target . interfaceRevisionId , obligation . required );
2026-09-16 00:12:39 -07:00
if ( ! satisfied )
issue (
issues ,
"unsatisfied-interface" ,
`packageImports[ ${ packageIndex } ]` ,
`Generic argument does not implement ${ obligation . required } ` ,
);
}
}
2026-09-03 15:46:52 -07:00
for ( const [ interfaceIndex , revision ] of workspace . interfaceImports . entries ()) {
const path = `interfaceImports[ ${ interfaceIndex } ]` ;
2026-09-16 00:12:39 -07:00
for ( const required of revision . requiredInterfaces ?? []) {
if ( ! indexes . interfaces . has ( required ))
issue ( issues , "unresolved-reference" , path , `Unknown prerequisite interface ${ required } ` );
2026-09-28 17:20:35 -07:00
else if ( interfaceImplies ( indexes , required , revision . revisionId ))
2026-09-16 00:12:39 -07:00
issue (
issues ,
"cyclic-conformance-requirement" ,
path ,
`Cyclic prerequisite involving ${ revision . revisionId } and ${ required } ` ,
);
}
for ( const obligation of revision . argumentRequirements ?? []) {
const satisfied =
obligation . target . kind === "atom"
? indexes . conformances . has ( conformanceKey ( obligation . target . atomId , obligation . required ))
2026-09-28 17:20:35 -07:00
: interfaceImplies ( indexes , obligation . target . interfaceRevisionId , obligation . required );
2026-09-16 00:12:39 -07:00
if ( ! satisfied )
issue ( issues , "unsatisfied-interface" , path , `Generic argument does not implement ${ obligation . required } ` );
}
2026-09-03 15:46:52 -07:00
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 );
2026-09-22 13:16:23 -07:00
} else if ( member . kind === "module" ) {
if ( member . subject ) validateConstraint ( issues , member . subject , ` ${ memberPath } .subject` , indexes );
2026-09-03 15:46:52 -07:00
} 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 );
}
}
}
}
};
2026-09-15 15:23:24 -07:00
const validateAttachments = ( issues : CapabilityValidationIssue [], indexes : ValidationIndexes ) => {
2026-09-03 15:46:52 -07:00
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 )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "unresolved-reference" , ` ${ path } .attachedTo` , `Unknown atom ${ attachment . attachedTo } ` );
2026-09-03 15:46:52 -07:00
}
validateValueType ( issues , attachment . valueType , ` ${ path } .valueType` , indexes );
2026-09-15 15:23:24 -07:00
const containsRpcType = ( type : ValueType ) : boolean =>
type . kind === "object-ref" ||
2026-09-19 22:20:55 -07:00
type . kind === "change" ||
( type . kind === "record" && Object . values ( type . fields ). some ( containsRpcType )) ||
2026-09-27 22:06:12 -07:00
( type . kind === "variant" && Object . values ( type . cases ). some (( entry ) => containsRpcType ( entry . value ))) ||
2026-09-15 15:23:24 -07:00
(( type . kind === "optional" || type . kind === "list" ) && containsRpcType ( type . value ));
if (
2026-09-28 17:20:35 -07:00
( ! [ "journal" , "subscription" ]. includes ( attachment . storagePolicy . kind ) &&
containsRpcType ( attachment . valueType )) ||
2026-09-15 15:23:24 -07:00
( attachment . storagePolicy . kind === "crdt-document" && containsRpcType ( attachment . storagePolicy . updateType ))
) {
issue (
issues ,
"invalid-attachment" ,
` ${ path } .valueType` ,
2026-09-19 22:20:55 -07:00
"Managed object references belong in graph relationships; captured changes are transport-only" ,
2026-09-15 15:23:24 -07:00
);
2026-09-10 18:27:41 -07:00
}
2026-09-03 15:46:52 -07:00
if ( attachment . storagePolicy . kind === "crdt-document" ) {
2026-09-15 15:23:24 -07:00
validateValueType ( issues , attachment . storagePolicy . updateType , ` ${ path } .storagePolicy.updateType` , indexes );
2026-09-03 15:46:52 -07:00
}
2026-09-28 17:20:35 -07:00
if ( attachment . storagePolicy . kind === "journal" ) {
if ( ! journalPayloadType ( attachment . valueType ) || attachment . defaultValue !== undefined )
issue ( issues , "invalid-attachment" , path , "Journals require finite typed payloads and start empty" );
const limits = attachment . storagePolicy . limits ;
const ceilings = {
maxEntries : 10000 ,
maxBytes : 1048576 ,
maxPublicationBytes : 262144 ,
maxProtectedSubscriptions : 256 ,
};
if (
! limits ||
Object . keys ( limits ). length !== 4 ||
Object . entries ( ceilings ). some (
([ key , ceiling ]) =>
! Number . isSafeInteger ( limits [ key as keyof typeof limits ]) ||
limits [ key as keyof typeof limits ] < 1 ||
limits [ key as keyof typeof limits ] > ceiling ,
) ||
limits . maxPublicationBytes > limits . maxBytes
)
issue ( issues , "invalid-attachment" , path , "Invalid bounded journal limits" );
}
if ( attachment . storagePolicy . kind === "subscription" ) {
validateValueType ( issues , attachment . storagePolicy . payload , ` ${ path } .storagePolicy.payload` , indexes );
if (
! journalPayloadType ( attachment . valueType ) ||
! journalPayloadType ( attachment . storagePolicy . payload ) ||
attachment . defaultValue !== undefined
)
issue (
issues ,
"invalid-attachment" ,
path ,
"Subscriptions require finite typed payloads/parameters and start unattached" ,
);
}
2026-09-03 15:46:52 -07:00
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 ) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-attachment" , ` ${ path } .endpoints` , `Edge ${ attachment . id } must have exactly two endpoints` );
2026-09-03 15:46:52 -07:00
}
for ( const [ endpointIndex , endpoint ] of attachment . endpoints . entries ()) {
const endpointPath = ` ${ path } .endpoints[ ${ endpointIndex } ]` ;
2026-09-15 15:23:24 -07:00
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" ,
);
2026-09-10 18:27:41 -07:00
}
2026-09-03 15:46:52 -07:00
requireText ( issues , endpoint . projectionId , ` ${ endpointPath } .projectionId` , "Projection ID" );
2026-09-10 18:27:41 -07:00
if ( endpoint . onDelete !== undefined && ! [ "restrict" , "detach" , "cascade-other" ]. includes ( endpoint . onDelete )) {
2026-09-15 15:23:24 -07:00
issue (
issues ,
"invalid-attachment" ,
` ${ endpointPath } .onDelete` ,
"Deletion policy must be restrict, detach, or cascade-other" ,
);
2026-09-10 18:27:41 -07:00
}
2026-09-03 15:46:52 -07:00
requireText ( issues , endpoint . displayName , ` ${ endpointPath } .displayName` , "Projection name" );
validateConstraint ( issues , endpoint . constraint , ` ${ endpointPath } .constraint` , indexes );
}
2026-09-15 15:23:24 -07:00
if ( attachment . endpoints . every (( endpoint ) => endpoint . keyType ))
issue (
issues ,
"invalid-attachment" ,
` ${ path } .endpoints` ,
"A v0 map has one keyed projection and one unkeyed inverse" ,
);
2026-09-03 15:46:52 -07:00
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 } ` ,
);
}
}
}
};
2026-09-15 15:23:24 -07:00
const uniquePrimitiveList = ( issues : CapabilityValidationIssue [], primitives : readonly string [], path : string ) => {
2026-09-03 15:46:52 -07:00
if ( primitives . length === 0 ) {
issue ( issues , "invalid-dependency-binding" , path , "Dependency port requires at least one primitive" );
}
const seen = new Set < string >();
for ( const primitive of primitives ) {
if ( seen . has ( primitive )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-dependency-binding" , path , `Dependency primitive ${ primitive } is listed more than once` );
2026-09-03 15:46:52 -07:00
}
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 );
2026-09-27 22:06:12 -07:00
if ( entry . preparation ) {
const port = entry . dependencyPorts . find (( port ) => port . id === entry . preparation ! . port );
if (
entry . execution ? . kind !== "transaction" ||
entry . kind === "constructor" ||
( entry . kind === "operation" && entry . mode !== "call" ) ||
! entry . preparation . key ||
port ? . requirement . kind !== "creation" ||
! valueTypesEqual ( port . requirement . inputType , entry . inputType ) ||
entry . outputType . kind !== "object-ref" ||
( entry . outputType . expectation . kind === "atom" &&
entry . outputType . expectation . atomId !== port . requirement . atomId )
)
issue (
issues ,
"invalid-dependency-binding" ,
exportPath ,
"Prepared creation requires a transaction returning its declared creation, with matching captured input types and a fixed key" ,
);
}
2026-09-20 21:51:43 -07:00
if ( entry . acceptanceExportId !== undefined ) {
const check = revision . exports . find (( candidate ) => candidate . id === entry . acceptanceExportId );
if (
entry . execution ? . kind !== "transaction" ||
! entry . execution . targets . includes ( "server" ) ||
! check ||
check . execution ? . kind !== "snapshot" ||
check . execution . targets . length !== 1 ||
check . execution . targets [ 0 ] !== "server" ||
check . acceptanceExportId !== undefined ||
entry . kind !== check . kind ||
entry . kind === "constructor" ||
! valueTypesEqual ( entry . inputType , check . inputType ) ||
! valueTypesEqual ( check . outputType , valueType . unit ) ||
( entry . kind === "operation" &&
( check . kind !== "operation" ||
check . mode !== "call" ||
! isDeepStrictEqual ( entry . receiverRequirement , check . receiverRequirement )))
)
issue (
issues ,
"invalid-operation" ,
` ${ exportPath } .acceptanceExportId` ,
"Acceptance requires a server snapshot export with matching kind, receiver and input, unit output, and no further acceptance hook" ,
);
}
2026-09-03 15:46:52 -07:00
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 < string >();
2026-09-15 15:23:24 -07:00
for ( const [
requirementIndex ,
interfaceRevisionId ,
] of entry . receiverRequirement . interfaceRevisionIds . entries ()) {
2026-09-03 15:46:52 -07:00
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 )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "unresolved-reference" , ` ${ exportPath } .constructsAtom` , `Unknown atom ${ entry . constructsAtom } ` );
2026-09-03 15:46:52 -07:00
}
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 ) {
2026-09-18 18:54:09 -07:00
case "reflection" :
if (
! port . requirement . selectors . length ||
new Set ( port . requirement . selectors ). size !== port . requirement . selectors . length ||
port . requirement . selectors . some (
2026-09-21 16:01:48 -07:00
( kind ) =>
! [ "active" , "atoms" , "interfaces" , "conformances" , "queries" , "indexes" , "presentations" ]. includes (
kind ,
),
2026-09-18 18:54:09 -07:00
)
)
issue ( issues , "invalid-dependency-binding" , portPath , "Reflection requires distinct supported selectors" );
break ;
2026-09-17 14:34:16 -07:00
case "query" :
if (
! indexes . packages
. get ( port . requirement . packageRevisionId )
? . revision . queries ? . some (
( query ) => port . requirement . kind === "query" && query . id === port . requirement . queryId ,
)
)
issue (
issues ,
"unresolved-reference" ,
portPath ,
`Unknown query ${ port . requirement . packageRevisionId } : ${ port . requirement . queryId } ` ,
);
break ;
2026-09-03 15:46:52 -07:00
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 ;
2026-09-05 12:33:52 -07:00
case "interface" :
2026-09-03 15:46:52 -07:00
if ( ! indexes . interfaces . has ( port . requirement . interfaceRevisionId )) {
issue (
issues ,
"unresolved-reference" ,
` ${ portPath } .requirement.interfaceRevisionId` ,
`Unknown interface revision ${ port . requirement . interfaceRevisionId } ` ,
);
}
break ;
2026-09-19 22:09:55 -07:00
case "creation" :
2026-09-03 15:46:52 -07:00
case "constructor" :
2026-09-15 15:23:24 -07:00
if ( port . requirement . inputType )
validateValueType ( issues , port . requirement . inputType , ` ${ portPath } .requirement.inputType` , indexes );
2026-09-03 15:46:52 -07:00
if ( ! indexes . atoms . has ( port . requirement . atomId )) {
issue (
issues ,
"unresolved-reference" ,
` ${ portPath } .requirement.atomId` ,
`Unknown atom ${ port . requirement . atomId } ` ,
);
}
}
}
}
}
};
const findAttachment = < Kind extends PersistentAttachment [ "kind" ] >(
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 < Conformance , "atomId" | "interfaceRevisionId" > | undefined ,
path : string ,
) => {
if ( ! canAccessAttachment ( entry . owner , conformance )) {
issue (
issues ,
"private-attachment-access" ,
path ,
`Attachment ${ entry . attachment . id } is private to conformance ${
2026-09-15 15:23:24 -07:00
entry . owner . kind === "conformance" ? ` ${ entry . owner . atomId } as ${ entry . owner . interfaceRevisionId } ` : ""
2026-09-03 15:46:52 -07:00
} ` ,
);
return false ;
}
return true ;
};
2026-09-04 21:03:09 -07:00
const validateTraversal = (
issues : CapabilityValidationIssue [],
indexes : ValidationIndexes ,
atomId : AtomId ,
traversal : { edgeTypeId : EdgeTypeId ; projectionId : EdgeProjectionId },
path : string ,
2026-09-05 12:33:52 -07:00
conformance? : Pick < Conformance , "atomId" | "interfaceRevisionId" >,
2026-09-04 21:03:09 -07:00
) => {
const attachment = findAttachment ( indexes , "edge" , traversal . edgeTypeId );
if ( ! attachment || attachment . attachment . kind !== "edge" ) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-dependency-binding" , ` ${ path } .edgeTypeId` , `Unknown traversal edge ${ traversal . edgeTypeId } ` );
2026-09-04 21:03:09 -07:00
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 ;
}
2026-09-15 15:23:24 -07:00
if ( ! projection . endpoint . publicTraversal )
validateAttachmentAccess ( issues , attachment , conformance , ` ${ path } .edgeTypeId` );
2026-09-04 21:03:09 -07:00
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 ;
};
2026-09-03 15:46:52 -07:00
const validateBoundDependencies = (
issues : CapabilityValidationIssue [],
params : {
dependencies : BoundDependency [];
dependencyPorts : DependencyPort [];
atomId : AtomId ;
conformance? : Pick < Conformance , "atomId" | "interfaceRevisionId" >;
path : string ;
indexes : ValidationIndexes ;
requirementGraph : Map < string , Set < string >>;
graphSourceKey? : string ;
},
) => {
const ports = new Map ( params . dependencyPorts . map (( port ) => [ port . id , port ]));
const bindings = new Map < string , BoundDependency >();
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 ) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-dependency-binding" , ` ${ path } .portId` , `Unknown dependency port ${ dependency . portId } ` );
2026-09-03 15:46:52 -07:00
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 ) {
2026-09-18 18:54:09 -07:00
case "reflection" : {
const selected = binding as Extract < DependencyBinding , { kind : "reflection" }>;
if (
selected . via ||
JSON . stringify ([... selected . selectors ]. sort ()) !== JSON . stringify ([... requirement . selectors ]. sort ())
)
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Reflection requires an explicit workspace binding with exactly the declared selectors" ,
);
break ;
}
2026-09-17 14:34:16 -07:00
case "query" : {
const selected = binding as Extract < DependencyBinding , { kind : "query" }>;
const query = params . indexes . packages
. get ( requirement . packageRevisionId )
? . revision . queries ? . find (( query ) => query . id === requirement . queryId );
if (
! query ||
selected . queryId !== requirement . queryId ||
selected . packageRevisionId !== requirement . packageRevisionId
) {
issue ( issues , "invalid-dependency-binding" , path , "Query port must bind its exact checked query export" );
break ;
}
2026-09-18 14:55:12 -07:00
if ( query . root . kind === "workspace-interface" ) {
if ( selected . scope !== "workspace" || selected . via )
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Workspace-interface queries require an explicit workspace scope without an object traversal" ,
);
break ;
}
if ( selected . scope !== "object" ) {
issue ( issues , "invalid-dependency-binding" , path , "Object-root queries require an object dependency binding" );
break ;
}
2026-09-17 14:34:16 -07:00
const traversal = selected . via
? validateTraversal (
issues ,
params . indexes ,
params . atomId ,
selected . via ,
` ${ path } .binding.via` ,
params . conformance ,
)
: undefined ;
const targets = selected . via
? traversal
? [... params . indexes . atoms . keys ()]. filter (( atom ) =>
atomSatisfiesConstraint ( atom as AtomId , traversal . target . constraint , params . indexes . conformances ),
)
: []
: [ params . atomId ];
for ( const atom of targets )
2026-09-18 14:55:12 -07:00
if ( ! params . indexes . conformances . has ( conformanceKey ( atom as AtomId , query . root . interface )))
issue (
issues ,
"unsatisfied-interface" ,
path ,
`Query root ${ atom } does not conform to ${ query . root . interface } ` ,
);
2026-09-17 14:34:16 -07:00
break ;
}
2026-09-03 15:46:52 -07:00
case "state" : {
const stateBinding = binding as Extract < DependencyBinding , { kind : "state" }>;
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` );
2026-09-28 17:20:35 -07:00
if ([ "journal" , "subscription" ]. includes ( attachment . attachment . storagePolicy . kind ))
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Journal storage requires checked journal interface calls, not ordinary state ports" ,
);
2026-09-04 21:03:09 -07:00
const traversal = stateBinding . via
? validateTraversal (
issues ,
params . indexes ,
params . atomId ,
stateBinding . via ,
` ${ path } .binding.via` ,
2026-09-05 12:33:52 -07:00
params . conformance ,
2026-09-04 21:03:09 -07:00
)
: undefined ;
if (
stateBinding . via
2026-09-15 15:23:24 -07:00
? traversal &&
! atomSatisfiesConstraint (
2026-09-04 21:03:09 -07:00
attachment . attachment . attachedTo ,
traversal . target . constraint ,
params . indexes . conformances ,
)
: attachment . attachment . attachedTo !== params . atomId
) {
2026-09-03 15:46:52 -07:00
issue (
issues ,
"invalid-dependency-binding" ,
` ${ path } .binding.slotId` ,
2026-09-04 21:03:09 -07:00
stateBinding . via
? `State ${ stateBinding . slotId } is not attached to the traversal target`
: `State ${ stateBinding . slotId } is not attached to receiver atom ${ params . atomId } ` ,
2026-09-03 15:46:52 -07:00
);
}
2026-09-19 22:20:55 -07:00
if (
requirement . primitives . includes ( "edit" ) &&
( attachment . attachment . storagePolicy . kind !== "crdt-document" ||
! valueTypesEqual ( attachment . attachment . valueType , attachment . attachment . storagePolicy . updateType ))
)
issue ( issues , "invalid-dependency-binding" , path , "Edit ports require a matching CRDT slot" );
2026-09-03 15:46:52 -07:00
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 < DependencyBinding , { kind : "edge" }>;
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 ;
}
2026-09-25 11:37:27 -07:00
if ( projection . target . keyType && requirement . primitives . includes ( "connect" ))
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Connect through the keyed projection so the caller supplies a map key" ,
);
2026-09-04 21:03:09 -07:00
const traversal = edgeBinding . via
? validateTraversal (
issues ,
params . indexes ,
params . atomId ,
edgeBinding . via ,
` ${ path } .binding.via` ,
2026-09-05 12:33:52 -07:00
params . conformance ,
2026-09-04 21:03:09 -07:00
)
: undefined ;
const originMatches = edgeBinding . via
2026-09-15 15:23:24 -07:00
? 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 );
2026-09-04 21:03:09 -07:00
if ( ! originMatches ) {
2026-09-03 15:46:52 -07:00
issue (
issues ,
"invalid-dependency-binding" ,
` ${ path } .binding.projectionId` ,
2026-09-04 21:03:09 -07:00
edgeBinding . via
? `Projection ${ edgeBinding . projectionId } cannot originate at the traversal target`
: `Projection ${ edgeBinding . projectionId } cannot originate at receiver atom ${ params . atomId } ` ,
2026-09-03 15:46:52 -07:00
);
}
if (
2026-09-28 17:20:35 -07:00
! constraintSatisfiesConstraint ( projection . target . constraint , requirement . target , params . indexes ) ||
2026-09-25 11:37:27 -07:00
requirement . cardinality !== projection . endpoint . cardinality ||
requirement . keyType !== projection . endpoint . keyType
2026-09-03 15:46:52 -07:00
) {
issue (
issues ,
"invalid-dependency-binding" ,
` ${ path } .binding` ,
2026-09-25 11:37:27 -07:00
`Edge port target, cardinality or key type does not match projection ${ edgeBinding . projectionId } ` ,
2026-09-03 15:46:52 -07:00
);
}
break ;
}
2026-09-05 12:33:52 -07:00
case "interface" : {
2026-09-15 15:23:24 -07:00
const interfaceBinding = binding as Extract < DependencyBinding , { kind : "interface" }>;
2026-09-20 22:05:59 -07:00
if ( interfaceBinding . receiver !== requirement . receiver || ( interfaceBinding . receiver && interfaceBinding . via )) {
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Reference-selected interface ports require an explicit matching from input binding" ,
);
break ;
}
2026-09-03 15:46:52 -07:00
if ( interfaceBinding . interfaceRevisionId !== requirement . interfaceRevisionId ) {
issue (
issues ,
"invalid-dependency-binding" ,
` ${ path } .binding.interfaceRevisionId` ,
`Interface port requires ${ requirement . interfaceRevisionId } ` ,
);
break ;
}
2026-09-22 21:57:35 -07:00
const selected = requirement . operations ? . map (
( id ) => params . indexes . interfaces . get ( requirement . interfaceRevisionId ) ? . operations . get ( id ) ? . operation ,
);
if (
selected ? . some (( op ) => op ? . scope === "class" ) &&
( interfaceBinding . via || interfaceBinding . receiver || selected . some (( op ) => op ? . scope !== "class" ))
) {
issue (
issues ,
"invalid-dependency-binding" ,
path ,
"Static interface dependencies require a receiverless selection of class operations on the bound atom" ,
);
break ;
}
2026-09-20 22:05:59 -07:00
// The reference supplies its own checked witness at invocation time.
// This is not a requirement that the current receiver implements the interface.
if ( interfaceBinding . receiver === "input" ) break ;
2026-09-05 12:33:52 -07:00
const traversal = interfaceBinding . via
? validateTraversal (
issues ,
params . indexes ,
params . atomId ,
interfaceBinding . via ,
` ${ path } .binding.via` ,
params . conformance ,
)
: undefined ;
const candidateAtoms = interfaceBinding . via
? traversal
2026-09-15 15:23:24 -07:00
? ([... params . indexes . atoms . keys ()]. filter (( atomId ) =>
atomSatisfiesConstraint ( atomId as AtomId , traversal . target . constraint , params . indexes . conformances ),
) as AtomId [])
2026-09-05 12:33:52 -07:00
: []
: [ 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 );
}
2026-09-03 15:46:52 -07:00
}
break ;
}
case "constructor" : {
const constructorBinding = binding as Extract < DependencyBinding , { kind : "constructor" }>;
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` ,
);
}
2026-09-08 14:27:24 -07:00
if ( requirement . inputType ) {
const selected = params . indexes . constructors . get ( requirement . atomId );
2026-09-15 15:23:24 -07:00
const implementation = selected
? params . indexes . packages . get ( selected . packageRevisionId ) ? . exports . get ( selected . exportId )
: undefined ;
2026-09-08 14:27:24 -07:00
if ( implementation && ! valueTypesEqual ( requirement . inputType , implementation . inputType )) {
2026-09-15 15:23:24 -07:00
issue (
issues ,
"invalid-dependency-binding" ,
` ${ path } .binding.atomId` ,
`Constructor port requires input ${ typeLabel ( requirement . inputType ) } , but selected constructor accepts ${ typeLabel ( implementation . inputType ) } ` ,
);
2026-09-08 14:27:24 -07:00
}
}
2026-09-03 15:46:52 -07:00
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 < string , Set < string >>;
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 ,
2026-09-05 12:33:52 -07:00
"unsatisfied-interface" ,
2026-09-03 15:46:52 -07:00
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 < string , Set < string >>();
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 )) {
2026-09-15 15:23:24 -07:00
issue ( issues , "unresolved-reference" , ` ${ path } .atomId` , `Unknown atom ${ conformance . atomId } ` );
2026-09-03 15:46:52 -07:00
}
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 < OperationId , Binding >();
2026-09-16 00:12:39 -07:00
for ( const required of interfaceEntry . revision . requiredInterfaces ?? []) {
const requiredKey = conformanceKey ( conformance . atomId , required );
if ( ! indexes . conformances . has ( requiredKey ))
issue ( issues , "unsatisfied-interface" , path , `Conformance requires ${ conformance . atomId } as ${ required } ` );
else requirementGraph . get ( key ) ? . add ( requiredKey );
}
2026-09-03 15:46:52 -07:00
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 ;
2026-09-18 16:59:19 -07:00
let binding : ConcreteBinding ;
if ( entry . binding . kind === "interface" ) {
const target = indexes . interfaces
. get ( entry . binding . interfaceRevisionId )
? . operations . get ( entry . binding . operationId ) ? . operation ;
if ( ! target || ! signaturesMatch ( operation , target ) || operation . scope !== target . scope ) {
issue (
issues ,
"invalid-forwarding-binding" ,
bindingPath ,
"Forwarding requires matching signatures, modes and receiver scopes" ,
);
continue ;
}
try {
binding = resolveForwardedBinding (
workspace ,
conformance . atomId ,
conformance . interfaceRevisionId ,
entry . operationId ,
). binding ;
} catch ( error ) {
issue ( issues , "invalid-forwarding-binding" , bindingPath , String ( error ));
continue ;
}
} else binding = entry . binding ;
2026-09-17 14:34:16 -07:00
const member = operationEntry . member ;
2026-09-28 10:28:49 -07:00
if ( member . kind === "value" && member . nativeField ) {
const primitive = { get : "read" , set : "write" , edit : "edit" }[ operation . displayName ];
const getter = member . operations . find (( o ) => o . displayName === "get" );
let read : Binding | undefined ;
try {
if ( getter )
read = resolveForwardedBinding (
workspace ,
conformance . atomId ,
conformance . interfaceRevisionId ,
getter . id ,
). binding ;
} catch {}
if (
binding . kind !== "state" ||
binding . primitive !== primitive ||
read ? . kind !== "state" ||
read . primitive !== "read" ||
read . slotId !== binding . slotId
)
issue (
issues ,
"invalid-state-binding" ,
bindingPath ,
"Field operations must resolve to the same native state slot" ,
);
}
2026-09-19 22:20:55 -07:00
if (
member . kind === "value" &&
operation . displayName === "edit" &&
( binding . kind !== "state" || binding . primitive !== "edit" )
)
issue (
issues ,
"invalid-state-binding" ,
bindingPath ,
"CRDT edit operations must resolve to a native state edit capability" ,
);
2026-09-17 14:34:16 -07:00
if (
member . kind !== "operation" &&
2026-09-22 13:16:23 -07:00
member . kind !== "module" &&
2026-09-17 14:34:16 -07:00
member . queryRead &&
operation . displayName === ( member . kind === "value" ? "get" : "resolve" )
) {
const native =
member . kind === "value"
2026-09-28 17:20:35 -07:00
? ( binding . kind === "state" && binding . primitive === "read" ) ||
( binding . kind === "journal" && binding . primitive === "head" ) ||
( binding . kind === "subscription" && binding . primitive === "status" )
2026-09-17 14:34:16 -07:00
: binding . kind === "edge" && binding . primitive === "resolve" ;
if ( ! native && ( member . queryRead . execution === "native" || binding . kind !== "package" ))
issue (
issues ,
"invalid-query-contract" ,
bindingPath ,
"This queryable contract requires a native read binding; a justification cannot weaken it" ,
);
2026-09-19 21:52:37 -07:00
else if ( ! native && member . queryRead . execution === "rpc-permitted" && ! entry . queryReason ? . trim ())
2026-09-17 14:34:16 -07:00
issue (
issues ,
"query-provider-reason-required" ,
bindingPath ,
"Package-backed query reads require a concrete query-reason justification" ,
);
}
2026-09-16 11:25:20 -07:00
if ( operation . scope === "class" && ( ! conformance . id || operation . mode !== "call" )) {
issue (
issues ,
"invalid-package-binding" ,
bindingPath ,
"Class capabilities require an explicit conformance ID and call mode" ,
);
continue ;
}
2026-09-18 16:59:19 -07:00
// The target conformance validates the concrete provider and its private
// authority. Forwarding exposes that operation, not its attachment ports.
if ( entry . binding . kind === "interface" ) continue ;
2026-09-16 11:25:20 -07:00
if ( operation . scope === "class" && binding . kind !== "package" ) {
issue (
issues ,
"invalid-package-binding" ,
bindingPath ,
"Class capabilities must bind a free package function, not instance storage" ,
);
continue ;
}
2026-09-20 22:41:51 -07:00
if ( binding . kind === "lifecycle" ) {
if (
binding . primitive !== "delete" ||
operationEntry . member . kind !== "operation" ||
operation . mode !== "call" ||
operation . inputType . kind !== "builtin" ||
operation . inputType . name !== "unit" ||
operation . outputType . kind !== "builtin" ||
operation . outputType . name !== "unit"
)
issue (
issues ,
"invalid-lifecycle-binding" ,
bindingPath ,
"Deletion implements an object call from unit to unit" ,
);
continue ;
}
2026-09-28 17:20:35 -07:00
if ( binding . kind === "journal" ) {
const attachment = findAttachment ( indexes , "state" , binding . slotId );
if (
! attachment ||
attachment . attachment . kind !== "state" ||
attachment . attachment . storagePolicy . kind !== "journal"
) {
issue ( issues , "invalid-state-binding" , bindingPath , "Expected native journal storage" );
continue ;
}
validateAttachmentAccess ( issues , attachment , conformance , bindingPath );
if (
attachment . attachment . attachedTo !== conformance . atomId ||
! [ "publish" , "readPage" , "head" ]. includes ( binding . primitive ) ||
! signaturesMatch ( operation , journalPrimitiveSignature ( attachment . attachment . valueType , binding . primitive ))
)
issue (
issues ,
"invalid-state-binding" ,
bindingPath ,
"Journal primitive does not match this object operation's signature" ,
);
continue ;
}
if ( binding . kind === "subscription" ) {
const attachment = findAttachment ( indexes , "state" , binding . slotId );
if (
! attachment ||
attachment . attachment . kind !== "state" ||
attachment . attachment . storagePolicy . kind !== "subscription"
) {
issue ( issues , "invalid-state-binding" , bindingPath , "Expected native subscription storage" );
continue ;
}
validateAttachmentAccess ( issues , attachment , conformance , bindingPath );
const slot = attachment . attachment ;
if ( slot . storagePolicy . kind !== "subscription" ) continue ;
if ( slot . attachedTo !== conformance . atomId )
issue ( issues , "invalid-state-binding" , bindingPath , "Subscription belongs to another atom" );
if ( binding . primitive === "attach" ) {
try {
subscriptionContract (
slot . storagePolicy . payload ,
slot . valueType ,
operation ,
[... indexes . interfaces . values ()]. map (( value ) => value . revision ),
);
} catch ( error ) {
issue ( issues , "invalid-state-binding" , bindingPath , String ( error ));
}
} else if (
! [ "status" , "pause" , "resume" , "retire" ]. includes ( binding . primitive ) ||
! signaturesMatch ( operation , {
mode : "call" ,
inputType : valueType.unit ,
outputType : binding.primitive === "status" ? subscriptionStatusType : valueType.unit ,
})
)
issue ( issues , "invalid-state-binding" , bindingPath , "Subscription primitive signature mismatch" );
continue ;
}
2026-09-03 15:46:52 -07:00
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` );
2026-09-28 17:20:35 -07:00
if ([ "journal" , "subscription" ]. includes ( attachment . attachment . storagePolicy . kind ))
issue ( issues , "invalid-state-binding" , bindingPath , "Use native journal primitives, not state access" );
2026-09-03 15:46:52 -07:00
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" ,
);
}
2026-09-19 22:20:55 -07:00
if (
binding . primitive === "edit" &&
( attachment . attachment . storagePolicy . kind !== "crdt-document" ||
! valueTypesEqual ( attachment . attachment . valueType , attachment . attachment . storagePolicy . updateType ))
)
issue (
issues ,
"invalid-state-binding" ,
bindingPath ,
"Edit requires a CRDT slot with a matching materialized profile type" ,
);
2026-09-03 15:46:52 -07:00
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 ;
}
const projection = edgeProjection ( attachment . attachment , binding . projectionId );
2026-09-25 11:37:27 -07:00
if ( projection ? . target . keyType && binding . primitive === "connect" )
issue (
issues ,
"invalid-edge-binding" ,
bindingPath ,
"Connect through the keyed projection so the caller supplies a map key" ,
);
2026-09-14 10:26:11 -07:00
// An owned endpoint may explicitly export read-only traversal, including
// a native inverse relationship view. This never exports mutation rights.
if ( ! ( binding . primitive === "resolve" && projection ? . endpoint . publicTraversal )) {
validateAttachmentAccess ( issues , attachment , conformance , ` ${ bindingPath } .binding.edgeTypeId` );
}
2026-09-15 15:23:24 -07:00
const relationshipMember = operationEntry . member . kind === "relationship" ? operationEntry.member : undefined ;
2026-09-04 21:03:09 -07:00
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 ;
2026-09-15 15:23:24 -07:00
const expected = edgePrimitiveSignature ( operationEdge , binding . projectionId , binding . primitive );
2026-09-03 15:46:52 -07:00
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 (
2026-09-28 17:20:35 -07:00
! constraintSatisfiesConstraint ( projection . target . constraint , operationEntry . member . target , indexes ) ||
2026-09-17 14:34:16 -07:00
operationEntry . member . cardinality !== projection . endpoint . cardinality ||
( operationEntry . member . keyType !== undefined && operationEntry . member . keyType !== projection . endpoint . keyType )
2026-09-03 15:46:52 -07:00
) {
issue (
issues ,
"invalid-edge-binding" ,
` ${ bindingPath } .binding` ,
2026-09-28 17:20:35 -07:00
`Edge projection target or cardinality does not match ${ conformance . atomId } / ${ conformance . interfaceRevisionId } / ${ operationEntry . member . displayName } ` ,
2026-09-03 15:46:52 -07:00
);
}
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 ;
}
2026-09-16 11:25:20 -07:00
if ( operation . scope === "class" ? packageExport . kind !== "function" : packageExport . kind !== "operation" ) {
2026-09-03 15:46:52 -07:00
issue (
issues ,
"invalid-package-binding" ,
` ${ bindingPath } .binding.exportId` ,
2026-09-16 11:25:20 -07:00
`Package export ${ binding . exportId } must be ${ operation . scope === "class" ? "a free function" : "an instance operation" } ` ,
2026-09-03 15:46:52 -07:00
);
continue ;
}
2026-09-16 11:25:20 -07:00
if (
! signaturesMatch ( operation , {
... packageExport ,
mode : packageExport.kind === "operation" ? packageExport . mode : "call" ,
})
) {
2026-09-03 15:46:52 -07:00
issue (
issues ,
"invalid-package-binding" ,
` ${ bindingPath } .binding.exportId` ,
2026-09-16 11:25:20 -07:00
`Package export signature does not match ${ describeSignature ( operation ) } ` ,
);
}
if ( packageExport . kind === "operation" )
validatePackageReceiver ( issues , {
entry : packageExport ,
atomId : conformance.atomId ,
path : ` ${ bindingPath } .binding.exportId` ,
indexes ,
requirementGraph ,
graphSourceKey : key ,
});
if (
operation . scope === "class" &&
2026-09-22 21:57:35 -07:00
packageExport . dependencyPorts . some (({ requirement }) => {
if ([ "constructor" , "creation" , "reflection" ]. includes ( requirement . kind )) return false ;
if ( requirement . kind !== "interface" ) return true ;
if ( requirement . receiver === "input" ) return false ;
const contract = indexes . interfaces . get ( requirement . interfaceRevisionId );
return (
! requirement . operations ? . length ||
! requirement . operations . every (( id ) =>
contract ? . revision . members
. flatMap (( m ) => m . operations )
. some (( op ) => op . id === id && op . scope === "class" ),
)
);
})
2026-09-16 11:25:20 -07:00
) {
issue (
issues ,
"invalid-package-binding" ,
bindingPath ,
2026-09-19 22:09:55 -07:00
"Class functions require receiverless dependencies such as creation ports" ,
2026-09-03 15:46:52 -07:00
);
}
validateBoundDependencies ( issues , {
dependencies : binding.dependencies ,
dependencyPorts : packageExport.dependencyPorts ,
atomId : conformance.atomId ,
conformance ,
path : ` ${ bindingPath } .binding` ,
indexes ,
requirementGraph ,
graphSourceKey : key ,
});
2026-09-20 21:51:43 -07:00
const check = packageEntry . exports . get ( packageExport . acceptanceExportId ! );
if ( check ) {
validateBoundDependencies ( issues , {
dependencies : binding.acceptanceDependencies ?? [],
dependencyPorts : check.dependencyPorts ,
atomId : conformance.atomId ,
conformance ,
path : ` ${ bindingPath } .binding.acceptance` ,
indexes ,
requirementGraph ,
graphSourceKey : key ,
});
} else if ( binding . acceptanceDependencies !== undefined )
issue ( issues , "invalid-package-binding" , bindingPath , "Acceptance ports require a declared acceptance export" );
2026-09-03 15:46:52 -07:00
}
2026-09-18 16:59:19 -07:00
for ( const member of interfaceEntry . revision . members ) {
const start = member . operations . find (( op ) => op . mode === "watch-start" );
const stop = member . operations . find (( op ) => op . mode === "watch-stop" );
if ( ! start || ! stop || ! [ start , stop ]. some (( op ) => bindings . get ( op . id ) ? . kind === "interface" )) continue ;
try {
const a = resolveForwardedBinding ( workspace , conformance . atomId , conformance . interfaceRevisionId , start . id );
const b = resolveForwardedBinding ( workspace , conformance . atomId , conformance . interfaceRevisionId , stop . id );
const target = indexes . interfaces
. get ( a . conformance . interfaceRevisionId )
? . revision . members . find (( m ) => m . operations . some (( op ) => op . id === a . entry . operationId ));
if ( a . conformance !== b . conformance || ! target ? . operations . some (( op ) => op . id === b . entry . operationId ))
issue (
issues ,
"invalid-forwarding-binding" ,
path ,
"Forwarded watch start/stop must resolve to the same target member" ,
);
const get = member . operations . find (( op ) => op . displayName === ( member . kind === "value" ? "get" : "resolve" ));
if ( get && ( a . binding . kind === "state" || a . binding . kind === "edge" )) {
const read = resolveForwardedBinding (
workspace ,
conformance . atomId ,
conformance . interfaceRevisionId ,
get . id ,
). binding ;
const matches =
a . binding . kind === "state"
? read . kind === "state" && read . slotId === a.binding.slotId
: read.kind === "edge" &&
read . edgeTypeId === a . binding . edgeTypeId &&
read . projectionId === a . binding . projectionId ;
if ( ! matches )
issue (
issues ,
"invalid-forwarding-binding" ,
path ,
"Native forwarded watch must observe its getter's storage" ,
);
}
} catch ( error ) {
issue ( issues , "invalid-forwarding-binding" , path , String ( error ));
}
}
2026-09-04 21:03:09 -07:00
const materializedMembers = new Set < string >();
2026-09-15 15:23:24 -07:00
for ( const [ materializationIndex , materialization ] of ( conformance . relationshipMaterializations ?? []). entries ()) {
const materializationPath = ` ${ path } .relationshipMaterializations[ ${ materializationIndex } ]` ;
2026-09-04 21:03:09 -07:00
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 );
2026-09-15 15:23:24 -07:00
const member = interfaceEntry . revision . members . find (( entry ) => entry . id === materialization . memberId );
2026-09-04 21:03:09 -07:00
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" ,
);
}
2026-09-15 15:23:24 -07:00
const resolveOperation = member . operations . find (( entry ) => entry . displayName === "resolve" );
const resolveBinding = resolveOperation ? bindings . get ( resolveOperation . id ) : undefined ;
2026-09-04 21:03:09 -07:00
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 ;
}
2026-09-15 15:23:24 -07:00
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
) {
2026-09-04 21:03:09 -07:00
issue (
issues ,
"invalid-relationship-materialization" ,
` ${ materializationPath } .constructedProjectionId` ,
"Materialization must connect through the opposite projection of the resolved edge" ,
);
continue ;
}
2026-09-15 15:23:24 -07:00
if ( ! atomSatisfiesConstraint ( materialization . constructorAtomId , member . target , indexes . conformances )) {
2026-09-04 21:03:09 -07:00
issue (
issues ,
"invalid-relationship-materialization" ,
` ${ materializationPath } .constructorAtomId` ,
`Constructed atom ${ materialization . constructorAtomId } does not satisfy the relationship target` ,
);
}
2026-09-15 15:23:24 -07:00
if (
! atomSatisfiesConstraint (
materialization . constructorAtomId ,
constructedProjection . endpoint . constraint ,
indexes . conformances ,
) ||
! atomSatisfiesConstraint ( conformance . atomId , constructedProjection . target . constraint , indexes . conformances )
) {
2026-09-04 21:03:09 -07:00
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` ,
);
2026-09-15 15:23:24 -07:00
} else if ( ! valueTypesEqual ( constructorExport . inputType , valueType . atomRef ( conformance . atomId ))) {
2026-09-04 21:03:09 -07:00
issue (
issues ,
"invalid-relationship-materialization" ,
` ${ materializationPath } .constructorAtomId` ,
`Component constructor must accept ${ typeLabel ( valueType . atomRef ( conformance . atomId )) } ` ,
);
}
}
2026-09-03 15:46:52 -07:00
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 < string , "visiting" | "visited" >();
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 < string , Set < string >>();
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 ) {
2026-09-15 15:23:24 -07:00
issue ( issues , "invalid-constructor" , ` ${ path } .exportId` , `Unknown export ${ constructor . exportId } ` );
2026-09-03 15:46:52 -07:00
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 ,
});
}
};
2026-09-15 15:23:24 -07:00
export const validateWorkspaceRevision = ( workspace : WorkspaceRevision ) : CapabilityValidationIssue [] => {
2026-09-03 15:46:52 -07:00
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 );
2026-09-21 11:23:26 -07:00
issues . push (
... validateExecutionContracts ( workspace ),
... validateInitialization ( workspace ),
... validatePresentations ( workspace ),
2026-09-22 13:16:23 -07:00
... validateModules ( workspace ),
2026-09-21 11:23:26 -07:00
... validateWorkspaceControllers ( workspace ),
);
2026-09-03 15:46:52 -07:00
return issues ;
};
export interface CompiledConformance {
source : Conformance ;
operationBindings : ReadonlyMap < OperationId , Binding >;
}
export interface CompiledWorkspaceRevision {
source : WorkspaceRevision ;
atoms : ReadonlyMap < AtomId , WorkspaceRevision [ "atoms" ][ number ] >;
interfaces : ReadonlyMap < InterfaceRevisionId , InterfaceRevision >;
packages : ReadonlyMap < PackageRevisionId , PackageRevision >;
attachments : ReadonlyMap < string , OwnedAttachment >;
conformances : ReadonlyMap < string , CompiledConformance >;
constructors : ReadonlyMap < AtomId , AtomConstructorBinding >;
}
export type CompileWorkspaceRevisionResult =
| { ok : true ; plan : CompiledWorkspaceRevision }
| { ok : false ; issues : CapabilityValidationIssue [] };
2026-09-15 15:23:24 -07:00
export const compileWorkspaceRevision = ( workspace : WorkspaceRevision ) : CompileWorkspaceRevisionResult => {
2026-09-03 15:46:52 -07:00
const snapshot = structuredClone ( workspace ) as WorkspaceRevision ;
const issues = validateWorkspaceRevision ( snapshot );
if ( issues . length > 0 ) {
return { ok : false , issues };
}
const attachments = new Map < string , OwnedAttachment >();
for ( const attachment of snapshot . sharedAttachments ) {
attachments . set ( attachmentKey ( attachment ), {
attachment ,
owner : { kind : "workspace" },
});
}
const conformances = new Map < string , CompiledConformance >();
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 ,
},
});
}
2026-09-15 15:23:24 -07:00
conformances . set ( conformanceKey ( conformance . atomId , conformance . interfaceRevisionId ), {
source : conformance ,
operationBindings : new Map ( conformance . operationBindings . map (( entry ) => [ entry . operationId , entry . binding ])),
});
2026-09-03 15:46:52 -07:00
}
return {
ok : true ,
plan : {
source : snapshot ,
atoms : new Map ( snapshot . atoms . map (( atom ) => [ atom . id , atom ])),
2026-09-15 15:23:24 -07:00
interfaces : new Map ( snapshot . interfaceImports . map (( revision ) => [ revision . revisionId , revision ])),
packages : new Map ( snapshot . packageImports . map (( revision ) => [ revision . revisionId , revision ])),
2026-09-03 15:46:52 -07:00
attachments ,
conformances ,
constructors : new Map ( snapshot . constructors . map (( entry ) => [ entry . atomId , entry ])),
},
};
};
export const resolveConformance = (
plan : CompiledWorkspaceRevision ,
atomId : AtomId ,
interfaceRevisionId : InterfaceRevisionId ,
2026-09-15 15:23:24 -07:00
) : CompiledConformance | undefined => plan . conformances . get ( conformanceKey ( atomId , interfaceRevisionId ));
2026-09-03 15:46:52 -07:00
export const resolveOperationBinding = (
plan : CompiledWorkspaceRevision ,
atomId : AtomId ,
interfaceRevisionId : InterfaceRevisionId ,
operationId : OperationId ,
2026-09-18 16:59:19 -07:00
) : ConcreteBinding | undefined => {
if ( ! resolveConformance ( plan , atomId , interfaceRevisionId ) ? . operationBindings . has ( operationId )) return undefined ;
return resolveForwardedBinding ( plan . source , atomId , interfaceRevisionId , operationId ). binding ;
};
2026-09-03 15:46:52 -07:00
export type ResolvedOperationPlan =
| {
kind : "state" ;
binding : Extract < Binding , { kind : "state" }>;
attachment : OwnedAttachment & { attachment : StateSlotDefinition };
}
| {
kind : "edge" ;
binding : Extract < Binding , { kind : "edge" }>;
attachment : OwnedAttachment & { attachment : EdgeDefinition };
}
| {
kind : "package" ;
binding : Extract < Binding , { kind : "package" }>;
packageRevision : PackageRevision ;
2026-09-16 11:25:20 -07:00
packageExport : PackageOperationExport | PackageFunctionExport ;
2026-09-03 15:46:52 -07:00
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 },
};
}
2026-09-28 17:20:35 -07:00
if ( binding . kind === "lifecycle" || binding . kind === "journal" || binding . kind === "subscription" ) return undefined ; // These native providers require the current kernel.
2026-09-03 15:46:52 -07:00
const packageRevision = plan . packages . get ( binding . packageRevisionId );
const packageExport = packageRevision ? . exports . find (
2026-09-16 11:25:20 -07:00
( entry ) : entry is PackageOperationExport | PackageFunctionExport =>
entry . id === binding . exportId && ( entry . kind === "operation" || entry . kind === "function" ),
2026-09-03 15:46:52 -07:00
);
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 < SlotId | EdgeTypeId >;
constructorAtomIds : AtomId [];
}
export const computeCapabilityClosure = (
plan : CompiledWorkspaceRevision ,
roots : Array < { atomId : AtomId ; interfaceRevisionId : InterfaceRevisionId } > ,
) : CapabilityClosure => {
2026-09-15 15:23:24 -07:00
const conformances = new Map <
string ,
{
atomId : AtomId ;
interfaceRevisionId : InterfaceRevisionId ;
}
> ();
2026-09-03 15:46:52 -07:00
const packages = new Set < PackageRevisionId >();
const attachments = new Set < SlotId | EdgeTypeId >();
const constructors = new Set < AtomId >();
const queued = [... roots ];
const visited = new Set < string >();
2026-09-15 15:23:24 -07:00
const sourceConformances = new Map ([... plan . conformances ]. map (([ key , value ]) => [ key , value . source ]));
2026-09-03 15:46:52 -07:00
2026-09-15 15:23:24 -07:00
const includeDependencies = ( atomId : AtomId , dependencies : readonly BoundDependency []) => {
2026-09-03 15:46:52 -07:00
for ( const dependency of dependencies ) {
switch ( dependency . binding . kind ) {
2026-09-17 14:34:16 -07:00
case "query" : {
packages . add ( dependency . binding . packageRevisionId );
const query = plan . packages
. get ( dependency . binding . packageRevisionId )
? . checkedQueries ? . find (
( query ) => dependency . binding . kind === "query" && query . declaration . id === dependency . binding . queryId ,
);
if ( dependency . binding . via ) attachments . add ( dependency . binding . via . edgeTypeId );
for ( const effect of query ? . effects ?? []) {
for ( const conformance of sourceConformances . values ())
if ( conformance . interfaceRevisionId === effect . interfaceRevisionId )
queued . push ({ atomId : conformance.atomId , interfaceRevisionId : conformance.interfaceRevisionId });
}
break ;
}
2026-09-03 15:46:52 -07:00
case "state" :
attachments . add ( dependency . binding . slotId );
2026-09-04 21:03:09 -07:00
if ( dependency . binding . via ) {
attachments . add ( dependency . binding . via . edgeTypeId );
}
2026-09-03 15:46:52 -07:00
break ;
case "edge" :
attachments . add ( dependency . binding . edgeTypeId );
2026-09-04 21:03:09 -07:00
if ( dependency . binding . via ) {
attachments . add ( dependency . binding . via . edgeTypeId );
}
2026-09-03 15:46:52 -07:00
break ;
2026-09-05 12:33:52 -07:00
case "interface" : {
if ( dependency . binding . via ) {
attachments . add ( dependency . binding . via . edgeTypeId );
}
const targetAtoms = dependency . binding . via
? (() => {
2026-09-15 15:23:24 -07:00
const attachment = plan . attachments . get ( `edge \ u0000 ${ dependency . binding . via ! . edgeTypeId } ` ) ? . attachment ;
2026-09-05 12:33:52 -07:00
if ( ! attachment || attachment . kind !== "edge" ) return [];
2026-09-15 15:23:24 -07:00
const projection = edgeProjection ( attachment , dependency . binding . via ! . projectionId );
2026-09-05 12:33:52 -07:00
return projection
? [... plan . atoms . keys ()]. filter (( candidate ) =>
2026-09-15 15:23:24 -07:00
atomSatisfiesConstraint ( candidate , projection . target . constraint , sourceConformances ),
)
2026-09-05 12:33:52 -07:00
: [];
})()
: [ atomId ];
for ( const targetAtom of targetAtoms ) {
queued . push ({
atomId : targetAtom ,
interfaceRevisionId : dependency.binding.interfaceRevisionId ,
});
}
2026-09-03 15:46:52 -07:00
break ;
2026-09-05 12:33:52 -07:00
}
2026-09-19 22:09:55 -07:00
case "creation" : {
for ( const owner of dependency . binding . owners )
queued . push ({ atomId : dependency.binding.atomId , interfaceRevisionId : owner.interfaceRevisionId });
break ;
}
2026-09-03 15:46:52 -07:00
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 ,
});
2026-09-16 00:12:39 -07:00
for ( const interfaceRevisionId of plan . interfaces . get ( root . interfaceRevisionId ) ? . requiredInterfaces ?? []) {
queued . push ({ atomId : root.atomId , interfaceRevisionId });
}
2026-09-22 13:16:23 -07:00
for ( const module of conformance . source . moduleBindings ?? []) {
packages . add ( module . packageRevisionId );
const member = plan . interfaces
. get ( root . interfaceRevisionId )
? . members . find (( member ) => member . id === module . memberId );
if ( member ? . kind === "module" && member . subject ? . kind === "interface" )
queued . push ({ atomId : root.atomId , interfaceRevisionId : member.subject.interfaceRevisionId });
}
2026-09-03 15:46:52 -07:00
for ( const binding of conformance . operationBindings . values ()) {
2026-09-28 17:20:35 -07:00
if ( binding . kind === "state" || binding . kind === "journal" || binding . kind === "subscription" ) {
2026-09-03 15:46:52 -07:00
attachments . add ( binding . slotId );
} else if ( binding . kind === "edge" ) {
attachments . add ( binding . edgeTypeId );
2026-09-18 16:59:19 -07:00
} else if ( binding . kind === "interface" ) {
queued . push ({ atomId : root.atomId , interfaceRevisionId : binding.interfaceRevisionId });
2026-09-20 22:41:51 -07:00
} else if ( binding . kind === "package" ) {
2026-09-03 15:46:52 -07:00
packages . add ( binding . packageRevisionId );
includeDependencies ( root . atomId , binding . dependencies );
const packageRevision = plan . packages . get ( binding . packageRevisionId );
const operation = packageRevision ? . exports . find (
2026-09-15 15:23:24 -07:00
( entry ) : entry is PackageOperationExport => entry . id === binding . exportId && entry . kind === "operation" ,
2026-09-03 15:46:52 -07:00
);
if ( operation ? . receiverRequirement . kind === "all-interfaces" ) {
2026-09-15 15:23:24 -07:00
for ( const interfaceRevisionId of operation . receiverRequirement . interfaceRevisionIds ) {
2026-09-03 15:46:52 -07:00
queued . push ({ atomId : root.atomId , interfaceRevisionId });
}
}
}
}
2026-09-15 15:23:24 -07:00
for ( const materialization of conformance . source . relationshipMaterializations ?? []) {
2026-09-04 21:03:09 -07:00
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 );
}
}
2026-09-03 15:46:52 -07:00
}
return {
conformances : [... conformances . entries ()]
. sort (([ left ], [ right ]) => left . localeCompare ( right ))
. map (([, conformance ]) => conformance ),
packageRevisionIds : [... packages ]. sort (),
attachmentIds : [... attachments ]. sort (),
constructorAtomIds : [... constructors ]. sort (),
};
};