@@ -306,6 +306,8 @@ func (s *Syncer) syncBatchesWithExisting(ctx context.Context, syncStore Store, s
306306 }
307307
308308 lookup := newImportIndexedLookup (syncStore )
309+ // Resolve every implementation relationship before any call edge. Interface
310+ // dispatch may depend on an implements edge stored in a later spool record.
309311 for _ , path := range edgeSpool .records {
310312 record , err := edgeSpool .readRecord (path )
311313 if err != nil {
@@ -315,7 +317,20 @@ func (s *Syncer) syncBatchesWithExisting(ctx context.Context, syncStore Store, s
315317 for _ , file := range record .Files {
316318 parsedFiles = append (parsedFiles , parsedSyncFile {filePath : file .FilePath , edges : file .Edges })
317319 }
318- if err := s .resolveAndUpsertEdges (ctx , syncStore , lookup , parsedFiles , stats ); err != nil {
320+ if err := s .resolveAndUpsertImplements (ctx , syncStore , lookup , parsedFiles , stats ); err != nil {
321+ return nil , err
322+ }
323+ }
324+ for _ , path := range edgeSpool .records {
325+ record , err := edgeSpool .readRecord (path )
326+ if err != nil {
327+ return nil , err
328+ }
329+ parsedFiles := make ([]parsedSyncFile , 0 , len (record .Files ))
330+ for _ , file := range record .Files {
331+ parsedFiles = append (parsedFiles , parsedSyncFile {filePath : file .FilePath , edges : file .Edges })
332+ }
333+ if err := s .resolveAndUpsertOtherEdges (ctx , syncStore , lookup , parsedFiles , stats ); err != nil {
319334 return nil , err
320335 }
321336 }
@@ -419,7 +434,25 @@ func (s *Syncer) stageBatch(ctx context.Context, syncStore Store, files map[stri
419434// @sideEffect upserts resolved graph edges through the sync store.
420435// @mutates graph edges, stats.Unresolved
421436func (s * Syncer ) resolveAndUpsertEdges (ctx context.Context , syncStore Store , lookup resolve.NodeLookup , parsedFiles []parsedSyncFile , stats * SyncStats ) error {
422- implementsEdges , otherByFile := partitionParsedSyncEdges (parsedFiles )
437+ if err := s .resolveAndUpsertImplements (ctx , syncStore , lookup , parsedFiles , stats ); err != nil {
438+ return err
439+ }
440+ return s .resolveAndUpsertOtherEdges (ctx , syncStore , lookup , parsedFiles , stats )
441+ }
442+
443+ // resolveAndUpsertImplements resolves only implementation relationships from one parsed file set.
444+ // @intent let staged reconciliation finish a global implements pass before resolving interface-dispatch calls.
445+ // @sideEffect upserts resolved implements edges through the sync store.
446+ // @mutates graph edges, stats.Unresolved
447+ func (s * Syncer ) resolveAndUpsertImplements (ctx context.Context , syncStore Store , lookup resolve.NodeLookup , parsedFiles []parsedSyncFile , stats * SyncStats ) error {
448+ var implementsEdges []graph.Edge
449+ for _ , parsed := range parsedFiles {
450+ for _ , edge := range parsed .edges {
451+ if edge .Kind == graph .EdgeKindImplements {
452+ implementsEdges = append (implementsEdges , edge )
453+ }
454+ }
455+ }
423456 for _ , edgeChunk := range splitEdgeChunks (implementsEdges ) {
424457 resolved , err := resolve .ResolveWithOptions (ctx , lookup , edgeChunk , s .opts )
425458 if err != nil {
@@ -434,6 +467,15 @@ func (s *Syncer) resolveAndUpsertEdges(ctx context.Context, syncStore Store, loo
434467 return err
435468 }
436469 }
470+ return nil
471+ }
472+
473+ // resolveAndUpsertOtherEdges resolves non-implements relationships after implementation state is complete.
474+ // @intent preserve file-local import warmup while making interface call resolution independent of spool record order.
475+ // @sideEffect upserts resolved call, import, contains, and related edges through the sync store.
476+ // @mutates graph edges, stats.Unresolved
477+ func (s * Syncer ) resolveAndUpsertOtherEdges (ctx context.Context , syncStore Store , lookup resolve.NodeLookup , parsedFiles []parsedSyncFile , stats * SyncStats ) error {
478+ _ , otherByFile := partitionParsedSyncEdges (parsedFiles )
437479 importsByFile := importEdgesByFile (otherByFile )
438480 for _ , parsed := range parsedFiles {
439481 edges := otherByFile [parsed .filePath ]
0 commit comments