Cross-repo API contracts

August 4, 2026 · View on GitHub

Gortex detects API contracts across repos and matches providers to consumers:

# After indexing, contracts are auto-detected
gortex track .

# Via MCP tools
contracts                        # list all detected contracts (default action)
contracts {action: "check"}      # find mismatches and orphans
Contract typeDetectionProviderConsumer
HTTP routesFramework annotations (gin, Express, FastAPI, Spring, etc.)Route handlerHTTP client calls (fetch, http.Get)
gRPCProto service definitionsService RPCClient stub calls
GraphQLSchema type/field definitionsSchemaQuery/mutation strings
Message topicsPub/sub patterns across Kafka, RabbitMQ, NATS, and Redis (KindTopic nodes, produces_topic / consumes_topic edges); dynamic topic names suppressedPublish callsSubscribe calls
WebSocketEvent emit/listen patternsemit()on()
Env varsos.Getenv, process.env, .env files, Terraform aws_lambda_function environment.variablesSetenv / .env / Terraform environment.variables keyGetenv / process.env
OpenAPISwagger/OpenAPI spec filesSpec paths(linked to HTTP routes)
Temporal workflowsGo SDK worker.RegisterActivity(WithOptions) / RegisterActivities / Java @ActivityInterface / @WorkflowInterface annotationsActivity / workflow function (carries temporal_role Meta)workflow.ExecuteActivity / ExecuteChildWorkflow / client.ExecuteWorkflow / handler & signal/query calls

Contracts are normalized to canonical IDs (e.g., http::GET::/api/users/{id}) and matched across repos to detect orphan providers/consumers and mismatches.

Temporal edge taxonomy

The Go and Java extractors tag Temporal call sites with a via Meta value on the EdgeCalls edge (plus temporal_kind and temporal_name); ResolveTemporalCalls rewrites the resolvable ones (temporal.stub / temporal.start) to the registered handler / workflow node. Because they are ordinary EdgeCalls, find_usages / get_callers / explain_change_impact traverse them with no temporal-specific code.

viaDirectionEmitted fromtemporal_kindResolved?
temporal.registerprovider tagworker.RegisterActivity(WithOptions) / RegisterWorkflow(WithOptions) / RegisterActivitiesactivity / workflowindexed, not rewritten
temporal.stubworkflow → activity / child-workflowworkflow.ExecuteActivity / ExecuteLocalActivity / ExecuteChildWorkflowactivity / workflowyes → registered handler
temporal.startservice → workflowclient.ExecuteWorkflow / SignalWithStartWorkflowworkflowyes → registered workflow
temporal.handlerworkflow exposesworkflow.SetQueryHandler / GetSignalChannel / SetUpdateHandler (+WithOptions)query / signal / updateprovider edge
temporal.signal-sendsender → running workflowworkflow.SignalExternalWorkflow / client.SignalWorkflowsignalconsumer edge
temporal.query-callcaller → running workflowclient.QueryWorkflowqueryconsumer edge

Extra Meta on these edges: temporal_registered_name (the RegisterOptions{Name} override that is the actual dispatch key), temporal_register_plural (a RegisterActivities(&Struct{}) registration whose exported methods are each promoted), and temporal_name_origin=env_default (a dispatch name resolved from an env-var-with-literal-default, landed at the speculative tier). Node roles are stamped as temporal_role (activity / workflow / activity_interface / workflow_interface / signal / query / update). Aliased import wf "go.temporal.io/sdk/workflow" receivers are canonicalised before detection.