Telecraft

Development setup

Everything CI runs, you can run locally. Nothing in the default test path needs Docker, a network, or a credential.

This page is the build, test and lint commands. To watch the product work against real collectors and a real backend, see the local development environment, which is the only thing here that wants Docker.

Prerequisites

  • Go 1.26 or later. go.mod declares go 1.26.1; CI uses the current stable release.
  • Node.js 24 or later, with the npm that ships with it. Only the console needs it.
  • Git. The renderer, the serving path, and the estate loaders all treat git as the source of truth.

Optional, and only for the suites they enable:

  • Chromium for Playwright, installed with npx playwright install chromium from console/.
  • A running Elasticsearch, Elastic Fleet, Kubernetes API server, or forge app credential for the live provider suites. Each suite skips when its variables are absent, so none of this is needed to work on the core.

Get the source

git clone https://github.com/telecraft-dev/telecraft.git
cd telecraft

Build and test the core

The three commands the Build and test job runs, in order:

go build ./...
go vet ./...
go test ./...

go test ./... covers every package, including the lints' own self-tests and the provider conformance kits. Two of those self-tests run their check over this repository, so go test ./... fails on a tracked binary or an unformatted Go file without your running either tool separately. The live provider suites are compiled and run by this command too, and skip themselves when their environment variables are absent, so a clean checkout gives you a green run with no setup.

Run the vendor-word lint

go run ./tools/vendorlint

The lint reads vendorlint.yaml at the repository root and prints one line per finding, exiting 1 when it finds any and 0 when it does not. A clean run prints how many files it scanned and exits 0.

Two flags exist: -config names the config file, relative to -root, and -root names the tree to scan. The defaults are vendorlint.yaml and ., which is what CI uses.

The lint scans cmd/, internal/, console/, README.md and docs/. It is the mechanical form of the neutral core boundary, so read Architecture before you change vendorlint.yaml.

Run the tracked-executable check

go run ./tools/binlint

The check asks git for the tracked files, reads the opening bytes of each, and fails when one declares itself a Mach-O, ELF or PE executable, naming the file. A clean run prints how many files it scanned and exits 0. One flag exists: -root names the repository to scan, defaulting to ..

It reads magic bytes rather than the executable bit, because the bit is set on every checked-in shell script and says nothing about what a file is. If it fails, the fix is git rm --cached on the path it names, plus a line in .gitignore so the same local build cannot be staged again. Build output from the tools themselves is already ignored: go build ./tools/vendorlint writes into the working directory, which is how a 3.4 MB binary came to live at the repository root (issue #122).

Run the Go formatting check

go run ./tools/fmtlint

The check asks git for the tracked .go files and fails when gofmt would rewrite one, naming it. A clean run prints how many files it scanned and exits 0. One flag exists: -root names the repository to scan, defaulting to ..

gofmt -w on the paths it names is the fix. go fmt ./... is the shorter form and covers the main module, but not the separate modules under docs/prototypes/, which the check reads and go build ./... does not.

Two things about this one are worth knowing.

Do not replace it with gofmt -l. That command names the offending files and then exits 0, so a step built on it reports success while printing the problem. Nothing else in the repository reads layout either: go build and go test compile and run the code, and go vet reports suspicious constructs rather than formatting, which is how three test files stayed unformatted for months (issue #146).

You do not have to remember to run it. Its self-test runs the check over this repository, so go test ./... fails on an unformatted file too. The separate command is there for when you want the check on its own, and for CI.

Golden files

Two suites compare output against checked-in golden files. Regenerate them deliberately, and read the resulting diff before you commit it:

TELECRAFT_UPDATE_GOLDEN=1 go test ./internal/renderer ./internal/expectation
go test ./internal/card -update

go test ./internal/card -update rewrites console/fixtures/card-contract.json, the one artefact both sides of the card data contract are held to. The Go engine writes it and the console's tests/card-contract.test.ts reads it, so a field added on one side without the other following is a failing test. If the shape changed rather than the fixture data, bump the contract version as well.

Run the CLI binaries

Four binaries live under cmd/:

go run ./cmd/telecraft            # the platform CLI; prints its usage with no arguments
go run ./cmd/blueprint-check .    # strict-load every Blueprint and Component in an estate
go run ./cmd/catalogue-import -tag v0.158.0
go run ./cmd/schema-registry-import -repo https://git.example/registry -ref v1.4.0

The last two are the two substrates on the one import pipeline. Each fetches a repository at a pinned ref, writes one atomic versioned artefact, and prints a coverage report of everything the walk saw. Re-running the same ref is a no-op, and a new ref writes a new artefact beside the old one rather than replacing it.

schema-registry-import reads an adopter's own registry repository, so it has no default -repo. Add -path when the registry manifest lives in a subdirectory rather than at the repository root. Both commands take -source to import a checkout that is already on disk, which is the path a tree carried across an air gap takes.

telecraft observe and telecraft check read their backend settings from flags, defaulting to the TELECRAFT_TELEMETRY_ENDPOINT and TELECRAFT_TELEMETRY_API_KEY environment variables. The endpoint defaults to http://localhost:9200 when neither the flag nor the variable is set.

Work on the console

All console commands run from console/:

cd console
npm ci

npm ci installs exactly what package-lock.json pins, which is what CI does. Use it rather than npm install so your tree matches the one CI tests.

To run the console against the fixture backend, in two terminals:

npm run backend    # the fixture backend on http://127.0.0.1:4700
npm run dev        # the console on http://localhost:5173, proxying /api

The fixture backend prints its sign-in credentials at start-up. The platform binary verifies PBKDF2 hashes from the estate's users.yaml instead, and telecraft passwd authors them.

The checks the Console job runs, in the order it runs them:

npm run typecheck           # tsc --noEmit
npm test                    # Vitest: the engine, the presentation store, shelf ordering, the card contract
npm run check:palette       # the design tokens against their contrast and colour-vision floors
npm run build               # tsc --noEmit, then vite build into dist/
npm run check:zero-cdn      # no external host in any built artefact
npm run check:bundle-budget # the entry chunk within its gzipped ceiling
npm run e2e                 # Playwright against dist/ and the fixture backend

npm run e2e needs a browser first:

npx playwright install chromium

Playwright starts the fixture backend itself, serving both the documented API and the built bundle from dist/, so run npm run build before npm run e2e.

npm run check:zero-cdn runs over dist/, so it also needs a build first. It fails on any external URL in a built artefact: HTML, CSS and SVG tolerate none, and JavaScript tolerates only the allowlisted never-fetched string literals the script documents. The Playwright suite enforces the same rule at runtime by intercepting every network request and failing on any host beyond the console's own origin.

npm run check:bundle-budget needs a build first for the same reason. It measures the gzipped size of the entry chunk, the module dist/index.html loads, and fails when that exceeds the ceiling the script states and argues for. The console page explains what the ceiling is holding.

To build the demo bundle and its snapshot, as the Demo snapshot and bundle job does:

npm run build:demo       # VITE_DEMO=1: the console reads a snapshot, not /api

then generate the snapshot beside it with go run ./cmd/telecraft snapshot. The console page covers what demo mode changes.

Captures

Everything the browser harness writes goes to one directory, console/test-results/, which playwright.config.ts sets as outputDir and console/.gitignore ignores. That covers the suite's traces and per-test artefacts, and it covers a screenshot you take by hand:

npm run backend -- --dist dist         # the API and the built bundle
npm run capture -- /estate estate      # writes test-results/estate.png
npm run capture -- /topology topology --full-page

The command takes a name rather than a path, so a capture cannot land anywhere else. Before it existed, a screenshot went wherever the relative path it was given resolved, which for a command run at the repository root was the repository root, and two of them were committed by accident (issue #99).

A route resolves against the fixture backend on port 4700, and against that backend the command signs the fixture user in the way the Playwright suite's setup project does, so a capture shows the console rather than the login gate. Pass a full URL to capture anything else, such as npm run dev on port 5173, which is captured exactly as it answers.

Playwright clears the directory at the start of every run, so a capture is a working file rather than an archive. Copy anything worth keeping out of the repository.

The live-backend suites

Four suites talk to a real system. Each one reads its configuration from the environment and calls t.Skip when it is absent, so the suite is a skip and never a failure on a machine without credentials. That discipline is ADR-0036's: an absent credential is not a contract violation.

Because they skip rather than fail, a green go test ./... does not mean the live suites ran. Read the test output when you expect them to.

Variables Suite Enables
TELECRAFT_TELEMETRY_LIVE_ENDPOINT go test ./internal/provider/telemetry/ -run Live -v -count=1 The Elasticsearch TelemetryProvider against a real cluster: reads, attribute names, self-telemetry, and metering. The suite writes to telecraft-live-* indices only. This is the one live suite CI runs on every pull request, against a service container.
TELECRAFT_ELASTICFLEET_LIVE_ENDPOINT, TELECRAFT_ELASTICFLEET_LIVE_APIKEY go test ./internal/provider/estate/ -run Live -v -count=1 The ElasticFleet EstateProvider against a real Elastic Fleet API: the estate read and the redaction contract. Not run in CI.
TELECRAFT_INVENTORY_LIVE_ENDPOINT, TELECRAFT_INVENTORY_LIVE_TOKEN go test ./internal/provider/inventory/ -run Live -v -count=1 The Kubernetes InventoryProvider against a real API server, for example kubectl proxy at http://127.0.0.1:8001. Not run in CI.
FORGE_APP_ID, FORGE_INSTALLATION_ID, FORGE_APP_PRIVATE_KEY, optionally FORGE_LIVE_REPO go test ./internal/provider/forge/ -run Live -v -count=1 The forge adapter's pull-request flow against a real repository. FORGE_LIVE_REPO overrides the default fixture repository. The suite skips twice over: once when the three credentials are not all set, and again when the app installation cannot see the repository.

Each skip message names the variables it wants and why, so a skipped run tells you what to provide.