Network Egress
chant sends no telemetry. Nothing in core or in any lexicon reports usage, versions, errors or identities anywhere, and there is no endpoint to opt out of.
This page lists all the network chant uses: every module that calls a network primitive, which command reaches it, and where it dials. The tables below are generated from test/egress-catalogue.ts, which the same test that guards the offline phases checks against the tree on every run. A module that starts calling fetch and is not in the catalogue fails CI.
The decision
Section titled “The decision”An air-gapped mode does not belong in core.
At apply time chant must reach the AWS, Azure, GCP, Kubernetes or Fly endpoint the project describes, so a mode that cut that off would leave chant nothing to deploy to. Every other connection an adopter needs to cut already has its own fix: committed spec snapshots for codegen, the forgejo lexicon’s mirror base for uses: resolution, the build archive’s load-image-on-host backend for registry-free delivery, and the emulators for a whole substrate.
So offline use is a pattern documented on this page, and a test guarantees the offline phases.
Phases with no egress
Section titled “Phases with no egress”A guarded row is executed by test/no-egress.e2e.test.ts over every project in the example corpus, with net.Socket.prototype.connect and globalThis.fetch replaced by throwing stubs. Adding a network call to one of these paths fails that test.
| Command | Behaviour | Guarded by test |
|---|---|---|
chant build | Discovery, evaluation and serialization. No network in process, except a k8sInclude URL whose digest is not yet cached (see above). One declaration puts a network-reaching child on this path — HelmRender with a repo, which renders the chart at synthesis time; see the shell-outs below. | yes |
chant build --fold | The same, with modules reduced to values instead of executed. No network in process. | yes |
chant build --sandbox | Project code is evaluated in a child process instead. Neither process reaches the network. | yes |
chant lint | Declarative rules and post-synth checks, over the built documents. No network in process. | yes |
chant scenario check | Scenarios are evaluated against a fixture snapshot, which is what replaces the live read. A scenario whose given is snapshot(env) rather than a file first runs git fetch for the lifecycle branch — see the shell-outs below. | yes |
chant search | Answers from the declared graph. --live and --at are the flags that read an estate, and they land in the apply phase above. | yes |
chant graph, describe, list | Projections of the same built graph. --live again is the opt-in. | static scan only |
Shell-outs on those paths
Section titled “Shell-outs on those paths”An in-process guard says nothing about a child process, so the offline phases’ shell-outs are enumerated instead. The guard records every spawn and fails on a binary that is not listed here.
Reaches a network is the column to read. Two of these do, and both are conditional — they need a declaration or a flag that a project either has or does not. Nothing else spawned on an offline path leaves the machine.
| Binary | Spawned by | Reaches a network | What it does |
|---|---|---|---|
git | chant lint | no | git check-ignore --stdin, to drop ignored paths from the file scan. Local repository read; no remote. |
child.mjs | chant build --sandbox, chant lint --sandbox | no | The esbuild-bundled sandbox child, forked under process.execPath with --permission and a closed environment (PATH, plus CHANT_ENV for the config child). Node’s Permission Model has no network flag, so the child is bounded by what the bundle contains rather than by a kernel gate — see Sandboxed Execution. |
git | chant scenario check | yes, conditionally | git remote then git fetch <remote> chant/lifecycle, and only when a scenario’s given is snapshot(env) rather than a fixture file. This one does reach the configured git remote — the same remote the repository is already cloned from. |
helm | chant build, on a project declaring HelmRender | yes, conditionally | helm version, then helm template <name> <chart> --include-crds [--repo <url> --version <v>]. A HelmRender composite resolves at synthesis time, so a project that declares one puts a chart render on its build path. With repo set, helm fetches the chart from that repository — this is the one build-path shell-out that reaches a network, on first synth only: the rendered manifests are cached under the render root (CHANT_HELM_RENDER_ROOT, default ~/.chant/helm-renders/) and every later build reads the cache. A project declaring no HelmRender never spawns it. |
Phases that do reach the network
Section titled “Phases that do reach the network”Reading and changing an estate
Section titled “Reading and changing an estate”chant lifecycle diff --live, plan, snapshot, apply, converge, chant search --live, chant import --live, chant workspace graph --live, chant workspace check --live, chant run and every component verb talk to the substrate they manage. The two workspace commands do it through each member’s own chant graph --live, so they reach what that member’s lexicon reaches, and they are the only workspace commands that do (chant workspace check without --live reads files only). This is the provider API, not a chant service — the same endpoint a console session or an SDK call would use, with the same credentials.
| Module | Primitive | Destination | Why |
|---|---|---|---|
packages/core/src/components/verbs/cloud-executor.ts | node:net | a declared cluster member’s bolt port | The agnostic wait-cluster-healthy verb probes each member’s port to decide quorum; injectable, so a test never opens a socket. |
packages/core/src/components/verbs/wait-verify.ts | fetch | the endpoint a component declares | wait-endpoint and health-gate poll the deployed thing they were pointed at; the fetcher is injectable for the same reason. |
lexicons/aws/src/api/read-client.ts | fetch | AWS Cloud Control and CloudFormation, or AWS_ENDPOINT_URL | The aws lexicon’s read transport, signed with SigV4 and retargetable at a local emulator; there is no AWS SDK in the tree. |
lexicons/aws/src/api/credential-chain.ts | fetch | the AWS IAM Identity Center portal (portal.sso.<region>.amazonaws.com) and SSO OIDC (oidc.<region>.amazonaws.com) | chant lifecycle whoami resolves an SSO profile as the AWS CLI does (#3954): it refreshes an expired cached session token through OIDC when it can, then trades the token for role credentials. A credential_process profile runs the configured command, which may reach whatever it reaches. |
lexicons/aws/src/op/activities/aws-apply.ts | fetch | AWS Cloud Control, or AWS_ENDPOINT_URL | awsApply writes the change set the plan produced, over the same transport the read client uses. |
lexicons/aws/src/receipt-store.ts | fetch | AWS SSM Parameter Store, or AWS_ENDPOINT_URL_SSM | Effect receipts are read before an effect step and written after it succeeds; the store is the aws implementation of core’s injectable receipt seam. |
lexicons/aws/src/agentcore/trace-fetch.ts | fetch | Bedrock AgentCore | awsAgentCoreFetchTrace pulls a session history and renders it as replay-trace text for a later cedar policy replay. |
lexicons/azure/src/api/read-client.ts | fetch | Azure Resource Manager, or a declared endpoint override | The azure lexicon’s read transport for observation and live import. |
lexicons/azure/src/op/activities/az-apply.ts | fetch | Azure Resource Manager, or a declared endpoint override | azApply performs ARM resource CRUD for the native local applier and for real subscriptions alike. |
lexicons/gcp/src/api/read-client.ts | fetch | Google Cloud REST APIs, or a declared endpoint override | The gcp lexicon’s read transport, resolving Config Connector kinds to their underlying REST resources. |
lexicons/gcp/src/op/activities/gcp-apply.ts | fetch | Google Cloud REST APIs, or a declared endpoint override | gcpApply performs the REST calls a Config Connector manifest implies. |
lexicons/cpln/src/api.ts | fetch | the Control Plane API, or a declared endpoint | The cpln lexicon’s read transport, injectable through CplnHttp so tests need no network. |
lexicons/grafana/src/api/client.ts | fetch | the Grafana HTTP API named by grafana.profiles.<env> or GRAFANA_URL | The grafana lexicon’s transport for lifecycle diff --live and import --from, injectable through GrafanaHttp so tests need no network. |
lexicons/fly/src/op/activities/fly-apply.ts | fetch | the Fly Machines API, or FLY_API_HOSTNAME (the mudflaps emulator) | flyApply creates, updates and destroys Machines; pointing it at the emulator is how the Fly tutorials run offline. |
lexicons/fly/src/op/activities/machine-release.ts | fetch | the app’s own health endpoint, at the URL a release step is given | flyMachineVerify checks that a released Machine’s app answers its health endpoint with that release (#2736). The Machines API calls go through fly-apply.ts’s client. |
lexicons/fly/src/components/fly-release.ts | fetch | the app’s own health endpoint, through flyMachineVerify | The fly-release and fly-rollback capabilities hand an injectable fetch to flyMachineVerify, so tests reach no network (#2736). |
lexicons/fly/src/op/activities/sprites.ts | fetch | the Sprites API, or SPRITES_BASE_URL | Sprite lifecycle activities — create, exec, destroy — for the Sprites Ops. |
lexicons/fly/src/op/activities/sprite-service-converge.ts | fetch | a box service’s own health URL, as the Op declares it | spriteServicesObserve probes each declared service’s health URL for a ConvergeOp, and spriteServiceRestart waits for it after a restart (#2778). The Sprites API calls go through sprite-services.ts. |
lexicons/fly/src/op/activities/sprite-fs.ts | fetch | the Sprites filesystem API, or SPRITES_BASE_URL | Stages an input file into a sprite and reads a result out, without shelling it through spriteExec. |
lexicons/fountain/src/op/activities/fountain-apply.ts | fetch | the fountain control-plane API, or a declared endpoint | fountainApply performs the lexicon’s resource CRUD. |
lexicons/fountain/src/op/runtime.ts | fetch | the fountain control-plane API, or a declared endpoint | chant run <op> --on fountain posts the run to a steward’s thread and tails its SSE stream. |
lexicons/render/src/op/activities/render-apply.ts | fetch | the Render API | renderApply performs the lexicon’s resource CRUD. |
lexicons/k8s/src/op/activities/argo.ts | fetch | an Argo CD server’s API | The Argo sync/wait activities drive an installed Argo CD rather than reimplementing it. |
lexicons/cedar/src/avp/client.ts | fetch | Amazon Verified Permissions | The AVP read transport, hand-rolled over the AWS JSON protocol for the same reason the aws lexicon is: no AWS SDK in a vendor-neutral lexicon’s dependency tree. |
packages/core/src/op/activities/http-check.ts | fetch | whatever URL the Op step declares | httpCheck is a generic probe step — the URL is the caller’s, and the activity has no default of its own. |
packages/core/src/op/decide-backend.ts | fetch | a Jev-compatible POST /v1/systemone server a backend declares | The decide activity asks a decision point’s model decider. It runs only as an Op step: no chant read calls a model (ws-052). |
packages/core/src/op/activities/workflow-audit.ts | fetch | api.github.com, or a declared GitHub host | The workflow-audit activity resolves an action reference to a commit so a pipeline can assert what it actually ran. |
packages/core/src/pr-forge.ts | fetch | the forge that hosts the pull request: api.github.com or $GITHUB_API_URL, a Forgejo instance’s API, or a GitLab instance’s API | The pull-request loop (#3183): chant components pr-plan --forge keeps one note on the pull request and sets a commit status, and pr-apply lists approving reviews and finds the pull request that merged a commit. reconcilePr’s comment and issue finding modes post through the GitHub and Forgejo clients too (#3757). terragucci and SQL Yodeler import it from @intentius/chant/pr-forge for their own notes, statuses and tracking issues (#3759). On a Forgejo or GitLab job, reconcilePr’s pull-request mode, lexiconUpgrade, rollback and proposeWorkspaceUpgrade find, open, edit and label their pull request through it, since gh pr speaks a GraphQL API Forgejo does not serve (#3798). Only those CI paths reach it, and each client takes an injected fetch, so no test opens a socket. |
packages/core/src/op/activities/reconcile.ts | fetch | the forge a reconcile finding is posted to: api.github.com or $GITHUB_API_URL, a Forgejo instance’s API, or $CI_API_V4_URL on GitLab | reconcilePr’s comment and issue finding modes keep one pull request comment, merge request note or issue (#2256, #2292, #3757): GitLab over its REST API here, GitHub and Forgejo through pr-forge.ts’s clients. Only an Op step in one of those modes reaches it. |
packages/core/src/op/activities/forge-pull-request.ts | fetch | the forge a Forgejo or GitLab job runs on: a Forgejo instance’s API, or $CI_API_V4_URL on GitLab | Picks the pr-forge.ts client that reconcilePr’s pull-request mode, lexiconUpgrade, rollback and proposeWorkspaceUpgrade open their pull request through on Forgejo and GitLab, where gh pr cannot (#3798), and passes it the caller’s fetch. GitHub, and a run outside CI, keep gh. |
packages/core/src/gate-resume-run.ts | fetch | the forge that runs the job waiting at a gate: $GITHUB_API_URL, a Forgejo instance’s API, or $CI_API_V4_URL on GitLab, as the pending fact recorded it | Gate resume (#3683): chant approve --resume and chant run resume read the waiting run’s state and re-run it (GitHub), retry its job (GitLab) or dispatch its workflow (Forgejo). Only those two commands reach it, and the client takes an injected fetch, so no unit test opens a socket. |
packages/core/src/cli/handlers/gate-resume.ts | fetch | the same forge as packages/core/src/gate-resume-run.ts, through it | The handlers of chant approve --resume and chant run resume (#3683) pass an injected fetch through to resumeGateRun; they open no connection of their own. |
packages/core/src/notify.ts | fetch | the URLs the environment variables named by an Op waves spec’s notify hold: a Slack or Microsoft Teams incoming webhook, or the team’s own webhook | Notify (#3761): chant run wave posts an event when a wave waits for an approval, refuses a moved plan or fails, and products built on chant send the same event. Only a spec with notify reaches it, a failed delivery never changes the exit code, and sendNotify takes an injected fetch, so no unit test opens a socket. |
packages/core/src/op/gate-review.ts | fetch | the forge that hosts the merged pull request: $GITHUB_API_URL, a Forgejo instance’s API, or $CI_API_V4_URL on GitLab | A pr-review wave gate (#3684): chant run wave finds the pull request that merged the applied commit and reads its reviews, its author and the reviewers’ access. Only a wave whose approval mode is pr-review reaches it, and the client takes an injected fetch, so no unit test opens a socket. |
packages/core/src/lifecycle/release-publish.ts | fetch | the registries and forges a member’s release.publish declares | chant release publish --yes (#4047, #4048) hands the global fetch to the OCI and forge targets as their default HTTP client. It only names the default; the requests are the targets’. Tests inject a fake fetch. |
packages/core/src/lifecycle/publish-oci.ts | fetch | an OCI registry a release.publish target names (GHCR, Docker Hub, any registry API v2) | chant release publish --yes pushes a release’s images by digest and, at the reveal, moves their tags (#4047, #4048). Tests run against a registry fake on 127.0.0.1. |
packages/core/src/lifecycle/publish-forge.ts | fetch | the GitHub, GitLab or Forgejo API a release.publish forge target names | chant release publish --yes uploads a release’s assets to a draft release and publishes it at the reveal (#4047, #4048). Tests inject a recording fetch. |
packages/core/src/op/activities/pipeline-audit.ts | fetch | a GitLab instance’s API | The pipeline-audit activity reads project and pipeline metadata for the same governance question on GitLab. |
lexicons/prometheus/src/api/client.ts | fetch | a Mimir, Cortex or Loki ruler, a plain Prometheus, or an Alertmanager, at the target a profile declares | The prometheus lexicon’s transport (#3371): lifecycle diff --live, import --from and the ruler ApplyOp target all read and write through it, with the declared tenant and credentials. It takes an injected PromHttp, so tests need no network. |
lexicons/prometheus/src/op/activities/rules-loaded.ts | fetch | a Prometheus at the declared URL, $PROMETHEUS_URL, or http://localhost:9090 | rulesLoadedObserve reads /api/v1/rules so a ConvergeOp can see whether each declared rule group is loaded and healthy (#3369). |
lexicons/prometheus/src/op/activities/rule-audit.ts | fetch | a Prometheus at the declared URL, $PROMETHEUS_URL, or http://localhost:9090 | ruleAudit, the activity behind RuleAuditOp, reads rule health and alerts and queries each rule selector to find series nothing writes (#3369). |
lexicons/prometheus/src/op/activities/silence.ts | fetch | an Alertmanager at the declared URL, $ALERTMANAGER_URL, or http://localhost:9093 | alertmanagerSilence and alertmanagerUnsilence create and expire the silence an Op step holds over a change (#3369). |
lexicons/otel/src/op/activities/collector-health.ts | fetch | a running collector’s own health_check, zpages and metrics endpoints, or the hosts an Op step names in their place | collectorHealthObserve probes the endpoints a collector’s config declares so a ConvergeOp can tell whether it is up (#3369). |
lexicons/otel/src/op/activities/github.ts | fetch | api.github.com release lists | CollectorAuditOp reads the collector and semantic-conventions release lists here (#3369). The sticky issue that CollectorAuditOp and RuleAuditOp keep, and the proposal pull request, go through git and gh child processes. |
lexicons/otel/src/op/activities/collector-audit.ts | fetch | raw.githubusercontent.com, for each built-in component’s metadata.yaml in the collector repositories | collectorAudit, the activity behind CollectorAuditOp, compares component stability at the pinned and the newest collector release (#3369). The release lists come through github.ts. |
packages/core/src/behaviour-http.ts | fetch | the behaviour engine an operator named in CHANT_BEHAVIOUR_ENGINE, where that address is an http or https URL | The first behaviour-engine adapter (#2359): chant graph --live --overlay POSTs the resource graph a lexicon rendered and reads back per-entity cost, headroom and resilience. Not a provider API and not a chant service — a third party the operator chose, and the only row here that dials one. Unset, nothing is dialled and the overlay refuses by name; the transport is injectable, so no test opens a socket. |
lexicons/sql/src/clickhouse/http.ts | fetch | a ClickHouse server’s HTTP interface: the one sql.profiles.<env> (or CLICKHOUSE_URL) binds the environment to, and on a ClickHouse pin move the pinned clickhouse/clickhouse-server container generation starts on a loopback port | The sql lexicon’s only client. describeResources reads system.databases and system.tables, and chant import --from adds SHOW CREATE per object; all reads. clickhouseApply (the clickhouse ApplyOp target) writes: the CREATE, ALTER and, under a prune, DROP statements a build’s classified changes need, and reads system.mutations while a background rewrite runs. Generation reads the pinned server’s system.* catalog only when the pin moves or npm run generate -- --force asks; every other generate, bundle, validate and build reads the committed snapshot and reaches nothing. |
lexicons/sql/src/postgres/live/client.ts | database-client-package | a Postgres server: the one sql.profiles.<env> (a postgres:// URL) or POSTGRES_URL binds the environment to | The Postgres dialect’s client, node-postgres (pg), loaded only when a Postgres server is read. describeResources, observeResourcesDeep and chant import --from read the catalog (pg_catalog, and pg_get_*def() for each object’s statement); all reads, in a session with an empty search_path. A ClickHouse-only project never loads it. |
Local emulators
Section titled “Local emulators”chant emulator up pulls a container image and then polls the container it just started on localhost. The poll is loopback traffic; the image pull is the container runtime’s, and is the only egress in the phase.
| Module | Primitive | Destination | Why |
|---|---|---|---|
lexicons/fly/src/op/activities/machines-local.ts | node:net | 127.0.0.1 only: a free port for a Machine’s service, and a connect that waits for it to listen | The opt-in running mode of the local Machines API runs each started Machine on this host and publishes its service on a loopback port (#2831). Only tests and a local runner reach it. |
scripts/api-call-proxy.mjs | node:http | the local floci emulator, on TARGET_PORT (default 4691), from a proxy bound to loopback | chant#2403’s counting reverse proxy. The scale bench’s cost number has to be measured rather than asserted, so test/scale-estate.sh points AWS_ENDPOINT_URL at this and it forwards each AWS Query POST to floci unchanged while counting it by Action. Loopback to loopback: it dials the emulator a developer already started, never a network chant does not control, and nothing on an adopter’s path runs it. |
packages/core/src/op/emulator-lifecycle.ts | fetch, node:net | the just-started emulator container on localhost, and 127.0.0.1 ports | The shared boot loop polls the container’s health endpoint until it answers, and hostPortInUse opens a connect to 127.0.0.1 to see whether a host port is taken before a container is bound to it; loopback only, and the image pull it depends on is the container runtime’s. |
lexicons/sql/src/op/activities/clickhouse-emulator.ts | fetch | the ClickHouse emulator’s HTTP endpoint, as chant emulator status prints it | The identity probe asks the server for its UUID and version (#3673) so a server of another origin on the same port is told apart from the emulator; the endpoint is the container chant emulator up started, on localhost. |
Code generation
Section titled “Code generation”chant dev generate, chant dev pinned-upgrade and chant vendor fetch upstream schemas. Every lexicon’s spec/fetch.ts runs here and nowhere else: the generated types and the committed spec snapshot are what a build reads, so a machine that never runs codegen never needs the endpoints below. chant dev pinned-upgrade is the one command here that queries api.github.com, and it is a lexicon-maintainer command — it moves a pin in a lexicon’s own source, and nothing on an adopter’s build, lint or apply path calls it.
| Module | Primitive | Destination | Why |
|---|---|---|---|
packages/core/src/codegen/fetch.ts | fetch | the upstream schema URL a caller passes | fetchWithCache/fetchWithRetry — the single transport under every lexicon’s spec/fetch.ts, the k8s CRD loader, chant vendor and chant dev pinned-upgrade. Caches to disk, bounds each attempt, and falls back to the committed snapshot when the endpoint is unreachable. |
lexicons/aws/src/spec/fetch.ts | fetch | the CloudFormation resource specification and Cloud Control schema endpoints | The aws spec fetch follows redirects itself rather than going through the shared cache helper; run by chant dev generate, never by a build. |
lexicons/k8s/src/crd/oci-chart.ts | fetch | the OCI registry of a type: "helm" CRD source (today ghcr.io, for the KubeMicroVM operator chart at its pinned version and layer digest) | Pulls a chart’s content layer over the registry API so k8s generation reads chart-only CRDs without needing helm on PATH (chant #3300). The bare fetch is the anonymous-token probe, which expects a 401; the token, manifest and blob go through fetchWithRetry. Run by npm run generate, never by a build. |
lexicons/aws/scripts/refresh-enum-overlay.ts | fetch | raw.githubusercontent.com, for the botocore service models the overlay’s own entries name | Diffs the curated enum overlay (chant #1497) against the botocore shapes its values were read from, and with --write restamps them. A maintainer command, reached only by hand or by just refresh-enum-overlay; generate, bundle, validate, build and the test suite all read the committed src/codegen/enum-overlay.json and reach nothing. |
lexicons/cpln/src/spec/snapshot-cli.ts | fetch | the Control Plane OpenAPI document | just snapshot refreshes the committed offline spec snapshot; run on a networked machine on purpose, and the snapshot is what everything else reads. |
lexicons/grafana/src/spec/fetch-cli.ts | fetch | raw.githubusercontent.com, for the grafana-foundation-sdk JSON Schemas at the pinned commit | just fetch-schemas re-downloads the vendored Grafana schemas when bumping GRAFANA_SCHEMA_PIN; a maintainer command. Generate, bundle, validate, build and the tests read the committed files under src/spec/schemas/ and reach nothing. |
lexicons/grafana/src/spec/fetch-units-cli.ts | fetch | raw.githubusercontent.com, for Grafana’s valueFormats/categories.ts at the tag in GRAFANA_UNITS_SOURCE | just fetch-units re-extracts the unit ids GRAF115 accepts when moving to a newer Grafana; a maintainer command. Build, lint and the tests read the committed src/spec/units.gen.ts and reach nothing. |
lexicons/azure/scripts/fetch-quickstart-templates.ts | fetch | the Azure quickstart-templates repository | Builds the ARM fixture corpus the azure serializer is tested against; a maintainer script, not part of the lexicon’s published surface. |
| Command | Spawns | Module | Destination | Why |
|---|---|---|---|---|
npm run generate in lexicons/sql after a ClickHouse pin move, or with --force | docker run | lexicons/sql/src/core/container.ts | the container registry, for clickhouse/clickhouse-server at the pinned tag and digest when the image is not already local | docker run starts the pinned server on a random loopback port so generation can read its catalog, and the container is removed afterwards. The pull is the only egress; the catalog read itself is loopback. |
Including a pinned URL in a build
Section titled “Including a pinned URL in a build”A k8sInclude whose source is an http(s) URL is fetched by chant build the first time the build sees its digest. The bytes must match the declared sha256 digest. They are cached under it in ~/.chant/includes/ (moved by CHANT_INCLUDE_CACHE_ROOT). Every later build reads the cache and reaches nothing. A project with no URL include, or with its includes already cached, builds offline.
| Module | Primitive | Destination | Why |
|---|---|---|---|
lexicons/k8s/src/include/resolve.ts | fetch | the URL a k8sInclude declares as its source | Fetches a pinned multi-document YAML at buildRoots() when no cached copy matches its sha256 digest. Bytes that do not match the digest fail the build and are not cached; matching bytes are cached by digest, so later builds are offline (chant #3566). |
Starting a project from a template
Section titled “Starting a project from a template”chant init --from <repo>@<ref> fetches one commit of a template repository, and nothing else in chant init reaches a network. chant init --template <name> renders a lexicon’s templates from the installed package and reaches nothing, and chant init --from <dir> copies a template directory already on disk and reaches nothing (#2647). The fetch is a git child process, so it is listed below as a shell-out rather than as a module.
| Command | Spawns | Module | Destination | Why |
|---|---|---|---|---|
chant init --from <repo>@<ref> | git fetch | packages/core/src/workspace/lineage-init.ts | the repository named in --from: a git URL, git@host:path, or https://github.com/<owner>/<name>.git for owner/name. A local path reaches nothing | git fetch --depth 1 <repo> <ref> into a scratch repository that is deleted afterwards. The commit and tree it resolves are the content address the lineage lock records (#2540). Credentials are git’s own, and GIT_TERMINAL_PROMPT=0 stops it asking for any. |
Upgrading a project from its template
Section titled “Upgrading a project from its template”chant workspace upgrade <scope> fetches the target version of a git template, and the commit the scope was made from, to rebuild the merge base. Everything after the fetch runs offline in a local worktree. A vendor scope is read from its source as chant vendor pull reads it, and a scope made from a directory reads the --to directory from disk and reaches nothing. The proposeWorkspaceUpgrade activity stages the same upgrade, then pushes a proposal branch and opens or edits a pull request. chant workspace adopt-lineage and chant workspace hash-index list a template’s tags and fetch the versions they compare, and chant workspace versions --available lists the tags of each git template. Every step is a git or gh child process, listed below as shell-outs.
| Command | Spawns | Module | Destination | Why |
|---|---|---|---|---|
chant workspace upgrade <scope> | git fetch | packages/core/src/workspace/lineage-upgrade.ts | the template repository the lineage lock records for the scope (source.url), or the one --source names when the upgrade moves the scope to another template. A local repository reaches nothing | git fetch --depth 1 <url> <ref> for the target version, then git fetch --depth 1 <url> <commit> for the commit the scope was made from, into a scratch repository deleted afterwards. The second fetch rebuilds the merge base, always from the scope’s recorded source; when a server refuses it, the upgrade goes on without a base for edited files. Credentials are git’s own, and GIT_TERMINAL_PROMPT=0 stops it asking for any (#2550, #2551). |
chant workspace adopt-lineage --from <repo>, chant workspace hash-index --from <repo> and chant workspace versions --available | git ls-remote | packages/core/src/workspace/lineage-hash-index.ts | the template repository named in --from, or for versions --available the one each family’s lock records. A local repository reaches nothing | git ls-remote --tags <url> lists the template’s tags and the commit each names, so a cached hash index is reused only for tags that still name the same commit, and versions can say how many versions are newer than each lock (#2551). |
chant workspace adopt-lineage --from <repo> and chant workspace hash-index --from <repo> | git fetch | packages/core/src/workspace/lineage-hash-index.ts | the template repository named in --from. A local repository reaches nothing | git fetch --depth 1 --no-tags <url> refs/tags/<tag>... for the version tags the index has to compute, or git fetch --depth 1 <url> <ref> for the one ref --from <repo>@<ref> names, then a cached winner’s tag again to re-check it, into a scratch repository deleted afterwards. Credentials are git’s own, and GIT_TERMINAL_PROMPT=0 stops it asking for any (#2551). |
an Op step running the proposeWorkspaceUpgrade activity, in branch or pull-request mode | git push | packages/core/src/op/activities/propose-upgrade.ts | the repository’s own remote (origin unless the step names another) | git push --force <remote> <commit>:refs/heads/<branch>, only for the proposal branch (default chant/upgrade/<scope>). The activity refuses the default branch, the base branch and the checked-out branch (#2550). |
an Op step running the proposeWorkspaceUpgrade activity, in pull-request mode | gh pr | packages/core/src/op/activities/propose-upgrade.ts | the forge that hosts the remote, through the GitHub CLI and its own credentials | gh pr list to find the scope’s open pull request, then gh pr create or gh pr edit with the staged summary and the patch digest (#2550). On a Forgejo or GitLab job the same calls go over REST through pr-forge.ts instead (#3798). |
Following a squash merge
Section titled “Following a squash merge”chant workspace graph --intent, graph --intent --record and chant workspace runs read git history locally and reach nothing, unless --follow-squash is given. Then a squash commit’s pull request head is fetched from origin when the clone lacks it, once per pull request, into refs/chant/pull/<n>/head where later reads find it (#3035). Its row is in the shell-out table below, since git does the fetching.
| Command | Spawns | Module | Destination | Why |
|---|---|---|---|---|
chant workspace graph --intent --follow-squash, chant workspace graph --intent --record --follow-squash and chant workspace runs --follow-squash | git fetch | packages/core/src/workspace/squash.ts | the clone’s origin remote, GitHub or Forgejo, which keeps each pull request’s head at refs/pull/<n>/head. A local repository reaches nothing | git fetch --no-tags origin +refs/pull/<n>/head:refs/chant/pull/<n>/head for each squash whose pull request head the clone lacks, in one fetch, so the walk can read the pull request’s original commits (#3035). Credentials are git’s own, GIT_TERMINAL_PROMPT=0 stops it asking for any, and the fetch times out after a minute. Without --follow-squash it never runs. |
Auditing a remote repository
Section titled “Auditing a remote repository”chant audit <url> reads someone else’s repository over a git host’s API. Auditing a local path reaches nothing.
| Module | Primitive | Destination | Why |
|---|---|---|---|
packages/core/src/audit/fetch.ts | fetch | an allowlisted git host’s API — GitHub, GitLab or Forgejo | The only audit module that touches the network: chant audit <url> pulls candidate files so the auditor can run on a URL. Hosts are allowlisted, redirects refused, and file count, size, bytes and time all capped. |
Tagging the commits that passed CI
Section titled “Tagging the commits that passed CI”chant ci tick reads each recent commit’s check runs from the forge, then fetches the branch and the ci/* tags from origin and pushes the tags it makes (#3573). It runs in the generated CI file, not on a developer’s path. chant ci last-green and chant ci list read local tags only and reach nothing, except that chant ci list --forge github reads the check runs of the commits that have no tag. A tick from chant runs (#4044) asks the forge for nothing and fetches chant/lifecycle instead.
| Module | Primitive | Destination | Why |
|---|---|---|---|
packages/core/src/workspace/ci-green-forge.ts | fetch | api.github.com, or $GITHUB_API_URL for GitHub Enterprise Server | chant ci tick lists every check run on each commit within ci.green’s window, to judge its phases by each run’s latest attempt (#3573). The client takes an injected fetch, so no test opens a socket. |
| Command | Spawns | Module | Destination | Why |
|---|---|---|---|---|
chant ci tick | git fetch | packages/core/src/workspace/ci-green.ts | the repository’s origin remote | git fetch --no-tags --prune origin +refs/heads/<branch>:refs/remotes/origin/<branch> +refs/tags/ci/*:refs/tags/ci/* brings the branch and the ci tags, pruning a ci tag deleted on the remote by hand, so the tick judges what the remote holds (#3573). Credentials are git’s own, and GIT_TERMINAL_PROMPT=0 stops it asking for any. |
chant ci tick | git push | packages/core/src/workspace/ci-green.ts | the repository’s origin remote | git push origin refs/tags/ci/green/<sha> ... pushes the tags the tick made, never forced, so a tag that already exists there is refused rather than moved (#3573). |
chant ci tick --source runs, or with ci.green.source set to runs | git fetch | packages/core/src/lifecycle/git.ts | the repository’s origin remote, or its first remote when there is no origin | git fetch <remote> chant/lifecycle:chant/lifecycle, never forced, brings the run ledger whose signed Op runs the tick judges (#4044). A local branch ahead of the remote is kept, and one that has diverged stops the tick. |
chant components pr-plan --report-url s3://... and chant components pr-apply --report-url s3://... | aws s3 | packages/core/src/pr-report-url.ts | the S3 bucket --report-url names, at the endpoint and region the AWS CLI reads from the job’s environment | aws s3 cp - s3://<bucket>/<prefix>/<pr>/<commit>/<file> copies the stage’s report, its change set and, with --events-json, its run events, the events again as each wave and component settles, so a reader outside CI can fetch them (#4007). Credentials are the CLI’s own; chant passes none and prints none. A file:// URL reaches nothing, and without --report-url it never runs. |
Repository maintenance
Section titled “Repository maintenance”Freshness checks that run in this repository’s own CI, against api.github.com. Nothing here sits on a path an adopter runs — no CLI command reaches these modules.
| Module | Primitive | Destination | Why |
|---|---|---|---|
packages/core/src/op/emulator-freshness.ts | fetch | api.github.com release metadata | Reports how far behind an emulator’s pinned image is. Advisory, never gating, and reached only from scripts/check-emulator-freshness.ts — no CLI command calls it. |
packages/core/src/workspace/conformance/broker.ts | fetch | the broker under test, at the base URL its start(env) returns | The broker conformance suite (#3164) makes each check’s request to the broker a maintainer points it at, as a box would. No CLI command or build, lint or apply path imports it; only a broker’s own test run does, and chant’s own test dials a loopback stand-in. |
scripts/dogwood-freshness.ts | fetch | api.github.com commit and tree metadata | Compares the committed dogwood pin against upstream for scripts/check-dogwood-freshness.ts. The transport is injectable, which is why the comparison half is unit-tested without a network. |
scripts/prior-art-sweep.ts | fetch | the rule indexes of the tools the audit rules credit: raw.githubusercontent.com, api.github.com directory listings, and a few tools’ own docs sites (checkov.io, PSRule for Azure, the Bicep linter page) | Diffs each credited tool’s published rule list against scripts/prior-art/snapshot.json so a rule that appears or disappears upstream, or a credit whose upstream id no longer resolves, is reported. Run by the monthly prior-art-sweep workflow and by hand; never by a CLI command or a test. |
67 modules across packages/, lexicons/, scripts/, ops/ call a network primitive directly. Every other module that reaches the network does so through one of them, or through a child process listed on this page.
Supply chain
Section titled “Supply chain”The tables above leave out how chant and its outputs arrive on a machine.
npm install reaches a registry, like any Node project. A private mirror or an offline cache serves chant the same way it serves anything else. The install path has no chant-specific endpoint.
An emitted pipeline references container images and, on GitHub and Forgejo, uses: action refs. Those are fetched by the CI runner, not by chant, at a time chant is not running. The forgejo lexicon resolves uses: against a configurable mirror base for exactly this reason, so a self-hosted Forgejo with a local action mirror emits refs that resolve inside the perimeter.
Container images that chant itself produces travel in the build archive, and the archive’s load-image-on-host backend delivers them to a host without a registry in between.
Severing each surface
Section titled “Severing each surface”| Surface | How to cut it |
|---|---|
| Codegen | Do not run chant dev generate. The generated types and the committed spec snapshot in each lexicon are what a build reads; a lexicon is fully usable on a machine that never fetches a spec. |
| Kubernetes CRDs | CRD YAML is baked in at generation time, so chant build on a project using third-party CRDs stays offline. See Add a Third-Party CRD. |
| Helm chart renders | Warm the render cache on a networked machine before going offline: run chant build once, and ~/.chant/helm-renders/ carries every later build. A HelmRender pointed at a local chart path instead of a repo needs no fetch at all. |
| Action references | Point the forgejo lexicon at a mirror base, and uses: refs resolve against it instead of a public forge. |
| Image delivery | Use the build archive’s load-image-on-host publish backend instead of a registry. |
| A whole substrate | Run against an emulator. chant emulator up needs the image once; everything after it is loopback. |
| Build-ledger referrers | Nothing to cut. Referrer lookup defaults to noopReferrerLookup, which reaches nothing; the oras-backed lookup is opt-in, because a query that was network-free should not silently start reaching a registry. |
How this page stays true
Section titled “How this page stays true”test/egress-catalogue.ts holds the catalogue as data. test/no-egress.e2e.test.ts checks it from three sides on every run:
- A scan of
packages/,lexicons/,scripts/andops/finds every module naming a network primitive, and it must equal the catalogue exactly. An addedfetchfails, and so does a stale row for a call that no longer exists. - Each row declares which primitives it reaches for, and the scan must agree.
- The generated block above must match what the catalogue renders, byte for byte. Refresh it with
npm run generate:egress-catalogue.
A prose audit is wrong after the next merge. The test suite checks this page on every run.
What this is not
Section titled “What this is not”It is not a security boundary against project code you do not trust.
The guard is a regression gate over chant’s own source, run by chant’s own CI, and every mechanism in it is defeatable by code that wants to defeat it. A module can import its own copy of node:http, or shell out. That is the same argument Sandboxed Execution makes for why --sandbox does not ship a bootstrap-time fetch patch and does not claim to block egress: against a hostile file, a userland patch stops nothing.
The two are answering different questions. The sandbox asks whether a repository chant does not own can reach out from inside a build; the answer is yes, and the deployment guidance there (a container with no route out, a network namespace, a seatbelt profile) closes it. This page asks whether chant itself reaches out on the paths you run every day; the answer is no, and a test fails the day that stops being true.