Telecraft

Quickstart

Two ways in, and they are in the order most people want them. Take the first one that fits and stop; neither is a step towards the other.

You run it One file and git A console on your machine
The command line One file A verdict in CI

Want to look before you build? https://demo.telecraft.dev is the real console over a public estate, read only and with no sign-in.

1. You run it

One downloaded file and git. No toolchain, no compiling, and nothing to configure before you can look at it.

Download the build for your machine from the latest release. It is one static file with no runtime dependencies and no installer.

version=v0.9.0
os=$(uname -s | tr '[:upper:]' '[:lower:]')   # linux, or darwin on a Mac
arch=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
base=https://github.com/telecraft-dev/telecraft/releases/download/$version

curl -fsSLO "$base/telecraft-$version-$os-$arch"
curl -fsSLO "$base/SHA256SUMS"
sha256sum --ignore-missing --check SHA256SUMS

chmod +x "telecraft-$version-$os-$arch"
mv "telecraft-$version-$os-$arch" telecraft

Keep the release's own name until the checksum has been checked. SHA256SUMS lists every binary on the page by that name, and --ignore-missing skips the ones you did not download, so renaming first leaves it with nothing to check and nothing checked.

On Windows, download telecraft-<version>-windows-amd64.exe from the same page. It is attached and it is not otherwise exercised: nothing in the project's CI runs a Windows build, so tell us if it misbehaves.

Prefer not to place a binary? The container image is the same artefact: docker run --rm -v "$PWD:/w" -w /w ghcr.io/telecraft-dev/telecraft:release wherever this guide says ./telecraft.

Serve an estate

An estate is a git repository, not a database. Clone the public demo one and serve it:

git clone https://github.com/telecraft-dev/estate-demo.git
./telecraft serve -estate estate-demo

It prints a sign-in it made up for itself:

serve: no users.yaml in this estate, so this process minted one sign-in for itself.
serve: It is not written anywhere and it dies with the process.
serve:   email     bootstrap@localhost
serve:   password  1338e8c8e5bcb67c2c093785e286ba73
serve: Add users.yaml to the estate to replace it.
console and API on http://127.0.0.1:4321

Open http://127.0.0.1:4321 and sign in with those two. The password is drawn fresh each time the process starts, it is written nowhere, and it only exists because the console is bound to an address only your machine can reach. An Instance anybody else can reach refuses to start without a users.yaml, and Run an Instance is where you write one.

You are now looking at four Services across six Tiers, with real findings on them. Explore the demo walks the surfaces.

2. The command line

The console is one way to read a verdict. The other is a JSON report and an exit code, which is what belongs in CI, in a cron job, or on a laptop. The same file does both.

Get an estate

If you skipped the section above, clone the public demo estate beside the binary:

git clone https://github.com/telecraft-dev/estate-demo.git

It holds a synthetic retailer's observability estate: four Services, six Tiers, a requirements library, one Exemption, and the two declared readings a repository can't hold for you (which collectors reported, and the running configuration each one reports).

Get a verdict

To get a verdict, run telecraft check. It loads the requirements library, judges every row of the estate, writes one JSON report to stdout, and sets its exit code from the result:

./telecraft check \
  -library estate-demo/requirements \
  -estate estate-demo/demo/rows.yaml \
  -exemptions estate-demo/exemptions \
  > report.json

No telemetry backend is reachable yet, so the report says what the check could not see. The summary at the end of report.json:

{
  "rows": 5,
  "failing_rows": 4,
  "counting_failures": 4,
  "waived": 0,
  "library_drift": 0
}

Four production rows come back unknown rather than green, and each one carries the reason:

{
  "requirement": "trace-identity",
  "outcome": "unknown",
  "severity": 2,
  "detail": [
    "traces reading unavailable: backend unreachable: Post \"http://localhost:9200/_msearch\": dial tcp [::1]:9200: connect: connection refused"
  ]
}

Not knowing is a normal state, and the report shows it as itself. It never rounds unknown up to a pass. The command exits 1, so a CI job that can't see the backend goes red instead of green.

Add a backend and get the real cross

A verdict built on configuration alone can only tell you what somebody intended. Point the check at a telemetry backend and it crosses that intent against what arrived.

Start a throwaway single-node Elasticsearch:

docker run -d --name telecraft-quickstart \
  -p 127.0.0.1:9200:9200 \
  -e discovery.type=single-node \
  -e xpack.security.enabled=false \
  -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
  docker.elastic.co/elasticsearch/elasticsearch:9.1.0

Wait for it to start, then seed telemetry for the demo estate's Services. Every document carries the Service name, the Environment, and the two identity attributes the demo's trace-identity requirement asks about:

ES=http://localhost:9200
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)

for index in logs-demo metrics-demo traces-demo; do
  curl -fsS -XPUT "$ES/$index" -H 'Content-Type: application/json' -d '{
    "mappings": {
      "dynamic_templates": [
        {"strings_as_keyword": {"match_mapping_type": "string",
                                "mapping": {"type": "keyword"}}}
      ],
      "properties": {"@timestamp": {"type": "date"}}
    }
  }' > /dev/null
done

doc() {
  echo '{"index":{}}'
  printf '{"@timestamp":"%s","resource":{"attributes":{"service.name":"%s","deployment.environment.name":"%s"}},"service":{"namespace":"%s"},"deployment":{"environment":{"name":"%s"}}}\n' \
    "$NOW" "$1" "$2" "$3" "$2"
}

# storefront/catalogue-web deliberately gets no traces, and
# storefront/search deliberately gets no metrics.
{ doc checkout/payments production checkout
  doc checkout/basket production checkout
  doc storefront/catalogue-web production storefront
  doc storefront/search production storefront
  doc checkout/payments staging checkout
} | curl -fsS -XPOST "$ES/logs-demo/_bulk?refresh=true" \
      -H 'Content-Type: application/x-ndjson' --data-binary @- > /dev/null

{ doc checkout/payments production checkout
  doc checkout/basket production checkout
  doc storefront/catalogue-web production storefront
  doc checkout/payments staging checkout
} | curl -fsS -XPOST "$ES/metrics-demo/_bulk?refresh=true" \
      -H 'Content-Type: application/x-ndjson' --data-binary @- > /dev/null

{ doc checkout/payments production checkout
  doc checkout/basket production checkout
  doc storefront/search production storefront
  doc checkout/payments staging checkout
} | curl -fsS -XPOST "$ES/traces-demo/_bulk?refresh=true" \
      -H 'Content-Type: application/x-ndjson' --data-binary @- > /dev/null

Run the same check again. -endpoint defaults to http://localhost:9200, so the command doesn't change:

./telecraft check \
  -library estate-demo/requirements \
  -estate estate-demo/demo/rows.yaml \
  -exemptions estate-demo/exemptions \
  > report.json
checkout/basket           production  compliant
checkout/payments         production  compliant
storefront/catalogue-web  production  broken_pipeline
storefront/search         production  compliant
checkout/payments         staging     compliant
{
  "rows": 5,
  "failing_rows": 1,
  "counting_failures": 2,
  "waived": 1,
  "library_drift": 0
}

storefront/catalogue-web has an OTLP receiver wired into a traces pipeline, and no spans arrived, so the finding is broken_pipeline, not not_configured:

{
  "requirement": "traces-delivered",
  "title": "Distributed traces are delivered",
  "requirement_level": "required",
  "owner": "platform-observability",
  "outcome": "broken_pipeline",
  "severity": 7,
  "detail": [
    "no traces received in the last 24h0m0s"
  ],
  "remediation": "Instrument the Service with an OpenTelemetry SDK or auto-instrumentation agent and point it at the collector's OTLP receiver. Spans arriving with no receiver configured means something is bypassing the managed collector.\n"
}

Somebody configured that pipeline on purpose, and it is silently not working.

storefront/search scores compliant while carrying one waived finding. An authored Exemption covers its missing metrics, and the summary keeps "waived": 1 visible, so a green built on waivers never passes for a clean green.

When you are done, remove the container:

docker rm -f telecraft-quickstart

Building it yourself

You do not need this to use Telecraft, and the download above is the supported way to get the CLI. Build from source when you are changing it: fixing something, adding a subcommand, or running an unreleased commit.

git clone https://github.com/telecraft-dev/telecraft.git
cd telecraft
go build -o telecraft ./cmd/telecraft

You need Go 1.26 or later. The binary this produces is the same one the release attaches, minus the version stamp the release build sets. Contributing covers the rest of the development setup.

What next