Deploy Module (metacall-deploy)
Feature-gated. Build with --features metacall-deploy.
Overview
The deploy subcommand scans polyglot projects for MetaCall load and client call sites. It partitions files into same-language pods, resolves external dependencies from lockfiles, and generates two deployment artifacts:
| Artifact | Description |
|---|---|
metacall.pods.json | Pod manifest (version 1.1) with deployment units, inter-pod edges, per-pair cut annotations, dependency lists, and AST metrics |
metacall.mesh.json | Function Mesh topology with SCC deployment units and call-site attribution |
With -f yaml the same documents are written as metacall.pods.yaml and
metacall.mesh.yaml. MetaCall core reads JSON only, so the YAML form is a
meta-ast artifact for people and for tooling that prefers YAML.
Usage
# Build with the feature enabled
cargo build --release --features metacall-deploy
# Generate manifests
meta-ast deploy <path> --out <output_dir>
# CI validation: verify every cut edge has an RPC stub entry
meta-ast deploy <path> --check
Options
| Flag | Default | Description |
|---|---|---|
-o, --out <dir> | . | Directory to write generated artifacts |
-f, --format <json|yaml> | json | Serialization format |
--check | off | Check mode: cut fairness plus a diff of both manifests against the committed documents; exits non-zero on any issue |
--max-pod-size <N> | 20 | Files per pod before rebalancing triggers |
Pipeline
run_deploy()
1. pipeline::analyze_graph() - discovery, extraction, graph assembly, scope resolution,
client call projection and SCC analysis in one pass
2. scan_call_sites() - MetaCall call-site detection over the extractions
3. inject_load_edges() - load edges into the graph, with path resolution
4. orphaned_config_diagnostics() - configuration files that no call site loads
5. partition_graph() - Union-Find over same-language Import and Reference edges
6. detect_cuts() - cross-language SCC cuts plus oversized pods
7. build_documents() - file and pod metrics, dependency resolution,
pod manifest and mesh annotation
8. write_documents() - atomic writes, or fairness plus manifest diff in --check mode
The run returns every diagnostic it collected to the caller, which applies the diagnostic policy and decides the exit status. An ecosystem answers dependency questions from one table of sources: lockfiles are preferred over manifests, a lockfile that does not carry the entry falls through to the manifest for Python, Node and Go, and only Python and Node answer from an immediate subdirectory.
Module map
src/deploy/
├── mod.rs Entry point: run_deploy(), DeployConfig, add_metacall_edge()
├── scanner.rs tree-sitter call-site detection, CallSite, CallSiteVariant
├── client_call.rs resolve_client_calls(), resolve_script_to_file() - two-phase client invocation resolution
├── pod.rs Union-Find partition_into_pods(), PodPartition, InterPodEdge
├── cut.rs find_cross_language_cuts(), find_oversized_pod_cut(), CutEdge
├── dependency.rs one table of lockfile and manifest sources plus one reader per format
├── metrics.rs compute_file_metrics(), compute_pod_metrics(), FileMetrics
├── manifest.rs generate_pod_manifest(), PodManifest, ManifestEdge
├── mesh.rs generate_mesh_annotation(), DeploymentUnit, CrossLanguageEdge
├── check.rs check_cut_fairness() - bijection check between cuts and rpc_stub edges
└── tags.rs LangId <-> MetaCall tag mapping (py, node, ts, c, cpp, rs, go)
Call Site Scanner
scanner::scan_file runs tree-sitter queries to detect MetaCall API load variants and client calls.
Supported variants
| Variant | Detected functions |
|---|---|
LoadFromFile | metacall_load_from_file, LoadFromFile (Go), bare use import (Rust), load::from_single_file (Rust) |
LoadFromMemory | metacall_load_from_memory, LoadFromMemory |
LoadFromPackage | metacall_load_from_package, LoadFromPackage |
LoadFromConfiguration | metacall_load_from_configuration, LoadFromConfiguration |
ClientCall | metacall, metacall_await, metacallfms (all); metacallv, metacallt, metacall_function (C/C++); Go metacall.Call / metacall.Await; Rust metacall::metacall, metacall_no_arg, metacall_untyped. Note: metacall_handle is excluded because argument layout varies per port |
Receiver resolution
A member call counts only when its receiver names the MetaCall runtime.
bindings::resolve classifies the nearest declaration of the receiver name in
the lexical scopes around the call, so all of these resolve:
import metacall as mc,import * as mc from "metacall",const mc = require("metacall")import { metacall as m } from "metacall",const { metacall: m } = require("metacall")import mc "metacall"(Go),use metacall as mc;(Rust),namespace mc = metacall;(C++)
Exported and parenthesized declarations bind like plain ones. A local
declaration of the same name shadows the binding, so a call through a local
mc is not a MetaCall site. The specifier must be metacall or a
metacall/... subpath; metacall.js is a file, not the runtime. C and Ruby
carry no module binding: only the bare API names count there.
Confidence scoring
| Case | Score |
|---|---|
| String literal argument | 1.0 |
| Unique match in load-confirmed files (Phase A) | 1.0 |
| Multiple matches in load-confirmed files (Phase A) | 0.8 |
| Unique match in global name index (Phase B) | 0.6 |
| Multiple matches in global name index (Phase B) | 0.5 |
| Computed argument / function name | 0.4 |
Language coverage
Queries cover all 9 supported languages: Python, JavaScript, TypeScript, TSX, C, C++, Rust, Go, and Ruby.
Client invocation resolution
client_call::resolve_client_calls resolves ClientCall targets after scanning:
- Phase A (Load-aware): Maps caller load sites to candidate target files, matching function names within loaded scope.
- Phase B (Global fallback): Searches a project-wide index of symbol names when Phase A finds no match. Emits a Warning diagnostic if no match exists.
Ambiguity resolution is deterministic: one edge per matching symbol in path-sorted order. Client-call edges are EdgeKind::Reference from calling file to target symbol node.
run_deploy also warns on orphaned metacall.json files that no LoadFromConfiguration call site references.
Pod Partitioning
pod::partition_into_pods uses Union-Find to group files into same-language deployment units.
Files sharing the same LangId and connected by Import or Reference edges join the same pod. Cross-language edges remain inter-pod edges. Ownership edges are excluded because they express file structure, not dependency.
Confidence fusion
When an Import edge and a Reference edge connect the same pod pair, the fusion keeps the stronger confidence (max), never the product. A repeated (source, target, kind) triple merges the same way.
Language tag mapping
| Language | Tag |
|---|---|
| Python | py |
| JavaScript | node |
| TypeScript / TSX | ts |
| C | c |
| C++ | c |
| Rust | rs |
| Go | go |
| Ruby | rb |
C and C++ share the c loader (libclang). Go has no MetaCall loader yet; deploy emits a warning and the tag stays for manifests.
Cut Detection
cut.rs implements two cut rules:
- Cross-language SCC cuts: Cuts the lowest-confidence internal edge when an SCC spans multiple languages (
CutReason::CrossLanguageScc). The manifest marks these as RPC stubs. - Oversized pod cuts: Greedy single-pass cut on pods exceeding the pod size limit (
DEFAULT_MAX_POD_SIZE, 20 files by default). Override the limit with--max-pod-size <N>.
External Dependency Resolution
dependency::resolve_dependencies collects external imports per pod and inspects project lockfiles and manifests.
| Language(s) | Resolver | Lockfile (preferred) | Manifest (fallback) |
|---|---|---|---|
| Python | classify_python | uv.lock, poetry.lock, Pipfile.lock | pyproject.toml, requirements.txt |
| JS / TS / TSX | classify_node_ecosystem | package-lock.json, yarn.lock, pnpm-lock.yaml | package.json |
| Rust | classify_rust | Cargo.lock | Cargo.toml |
| Go | classify_go | go.sum | go.mod |
| C / C++ | classify_c_cpp_best_effort | - | conanfile.txt, vcpkg.json |
Lockfiles supply pinned versions (source: "Lockfile"). Manifest fallbacks set version: None (source: "Manifest").
Pod Manifest Schema
manifest::generate_pod_manifest writes metacall.pods.json:
{
"version": "1.1",
"deployments": [
{
"id": 0,
"language": "py",
"files": ["auth.py", "__init__.py"],
"metrics": {
"total_ast_nodes": 63,
"file_count": 2,
"symbol_count": 2
},
"dependencies": [
{
"name": "requests",
"version": "2.32.3",
"language": "python",
"source": "Lockfile"
}
]
}
],
"edges": [
{
"from_pod": 0,
"to_pod": 1,
"kind": "import",
"confidence": 1.0,
"is_cross_language": true,
"cut_annotations": []
}
],
"metrics": {
"total_pods": 2,
"cross_language_edges": 1,
"total_ast_nodes": 112
}
}
Mesh Annotation
mesh::generate_mesh_annotation exports SCC analysis to metacall.mesh.json.
#![allow(unused)]
fn main() {
MeshAnnotation {
version: String,
deployment_units: Vec<DeploymentUnit>,
cross_language_edges: Vec<CrossLanguageEdge>,
stats: MeshStats,
}
DeploymentUnit {
id: usize,
symbols: Vec<UnitSymbol>,
is_cross_language: bool,
is_mesh_candidate: bool,
deployability: String,
}
CrossLanguageEdge {
from_unit: usize,
to_unit: usize,
from_language: String,
to_language: String,
call_site: Option<String>,
confidence: f64,
}
}
Units with is_mesh_candidate = true and is_cross_language = false deploy independently as Function Mesh services.
Check Mode (Fairness)
check::check_cut_fairness validates RPC stub contracts:
- Every cut edge appears in
manifest.edges[]with cut annotations. - Cut edges have
kind: "rpc_stub". - Non-cut edges carry no cut annotation.
- The stub matches the cut direction; a reverse-direction stub alone fails.
Cut annotations land on the manifest edge between the cut’s own files, with
one rpc_stub summary per pod pair. The stub confidence is the weakest
finite link, or zero when no link is finite. Each cut_annotations list is in
canonical order by source file, target file, cut reason, then confidence
(total order), so permuted cut inputs serialize identically with no cuts dropped.
Check mode also diffs both generated documents against the committed
metacall.pods and metacall.mesh files when they exist; a missing
document skips the diff, so a fresh tree still checks clean.
run_deploy exits non-zero if fairness checks fail.
Edge-case Fixtures
Integration tests use fixtures in tests/fixtures/mixed/:
| Fixture | Coverage |
|---|---|
auth_microservice | Baseline acyclic star graph (py loads go/node/ts). |
auth_microservice_level2 | Cross-language SCC cycle, intra-language cycle collapse, dynamic and config loads. |
auth_microservice_level3 | All four load variants, 3-file cycle, py-go round-trip cut, lockfile classification. |
Extending the Scanner
To add call site detection for a new language:
- Add a
static <LANG>_QUERY: LazyLock<Query>inscanner.rs. - Add a dispatch arm to
scan_file. - Map tags in
tags.rs. - Add unit tests in
scanner.rs.