| name | integration-test |
| description | Run a live-instance verification of traceway-cli that goes beyond the Go smoke suite — exercises real-data detail endpoints, TTY-default rendering, adaptive metric-name discovery, and emits a human-readable coverage report. Invoke ONLY when the user explicitly asks (e.g. "run integration tests", "verify the CLI against stormwind"). Never invoke automatically after edits or commits. Assumes the user is already authenticated and a default project is configured. |
integration-test — traceway-cli
A repeatable protocol for verifying the CLI end-to-end against a live Traceway server, focused on the things the Go smoke suite can't easily cover.
Trigger
Invoke ONLY when the user explicitly asks. Never run as a side effect of edits, commits, or builds.
Relationship to the Go smoke suite
The just smoke-test target (test/smoke/*_test.go, build tag smoke) is the primary regression check. Against a live instance it already covers:
- JSON shape of every list endpoint and
profiles list.
- Output-format coverage (json/table/yaml) for projects, profiles, exceptions, endpoints, logs.
- Client-side enum validation (
--search-type, --order-by, --sort-direction, --aggregation, --tag).
- Time-range parsing edges (
--since 7D, missing --to, --since + absolute mix, far-future windows).
metrics query missing --name, bogus name → empty series, malformed tag.
exceptions show <zeros> → exit 5 not_found.
--profile no-such-profile → exit 4; --project 0…0 → non-zero, no panic, no connection_failed.
Do not re-implement these here. If they regress, that's a Go-test bug, not a skill failure.
What this skill adds beyond just smoke-test:
- Real-data detail endpoints —
exceptions show <captured-hash>, populated metrics query --name <real> with every aggregation + group-by.
- Adaptive metric-name discovery — walk a candidate list until one populates.
- TTY-vs-pipe default — table rendering to a real terminal.
- Coverage matrix report — a human-readable artifact, on demand.
- Safety doctrine — the forbidden-verb blocklist and
confirmMutation env hygiene, applied to every probe.
Hard constraints
Read-only. No exceptions. Even if a subcommand looks safe by name, check --help for mutating flags before running.
Forbidden verbs and flags
Skip any subcommand whose name or --help mentions:
archive, unarchive, resolve, unresolve, mute, ack, acknowledge
create, delete, update, set, put, post, add, remove, rm
assign, claim, close, reopen
login, logout, token, rotate, regenerate
--archive, --resolve, --delete, --write, --mutate, --apply, --commit
If a new subcommand is ambiguous (sync, refresh, replay, export), do not run it — list it under "skipped — manual review". If --dry-run exists, still skip write-shaped subcommands.
Mutation safeguards
The CLI gates mutations via confirmMutation (cmd/traceway/querycommon.go). The harness MUST:
- Never pass
--yes.
unset TRACEWAY_ASSUME_YES at the top of the script.
- Run with stdin from
/dev/null.
So that if a forbidden verb slips through, the gate refuses with exit 2 usage_error instead of hanging on a prompt.
Pre-flight
Run in order. Stop if any fails.
- Build:
nix develop --command go build -o ./bin/traceway ./cmd/traceway.
- Config exists (don't print — JWT inside):
test -f "${XDG_CONFIG_HOME:-$HOME/.config}/traceway/config.json".
- Reachability + capture
TW_PROJECT_ID:
./bin/traceway projects list --output json | jq -e 'type=="array" and length>=1' >/dev/null
TW_PROJECT_ID=$(./bin/traceway projects list --output json | jq -r '.[0].id')
projects list --output json returns a bare array, not a {data, pagination} envelope.
Detail-endpoint probes
exceptions show <captured-hash>
- Capture a real hash:
HASH=$(./bin/traceway exceptions list --since 720h --page-size 1 --output json | jq -r '.data[0].exceptionHash // empty')
If empty, retry against other projects via --project <id>. If still empty, skip with reason no exception found across all projects.
- With a real hash: three output formats +
--help. JSON shape: {group: {...}, occurrences: [...], pagination: {...}} — assert .group and .occurrences.
- Capture
.occurrences[0].traceId if present for the logs probe below.
metrics query --name <real-metric> (adaptive)
Probe these names in order until one returns a populated series:
system.cpu.utilization
system.network.io
system.network.errors
system.network.dropped
http.server.duration
traceway.requests
If none populates, skip the live block with reason no live metric name found.
For the first metric that populates:
- Three output formats +
--help.
- All aggregations:
avg, sum, count, min, max, p50, p95, p99.
--interval-minutes 15.
--group-by direction for network metrics (splits __all__ into receive/transmit).
JSON shape: {results: [{name, unit, series: {<tag-key>: [{timestamp, value}, ...]}}]} — series is a map keyed by group tag, default key __all__.
logs query --trace-id <captured>
If a real trace id was captured above, run logs query --trace-id $TRACE --since 720h. Assert exit 0 and {data, pagination} shape.
By-id detail commands (captured id + recordedAt)
These all require a timestamp flag; capture the id and its recordedAt together from exceptions show, then exercise them. All read-only.
- Capture one occurrence's id + recordedAt (+ optional trace/session ids):
OCC=$(./bin/traceway exceptions show "$HASH" --output json | jq -c '.occurrences[0]')
OID=$(jq -r '.id' <<<"$OCC")
OTS=$(jq -r '.recordedAt' <<<"$OCC")
DT=$(jq -r '.distributedTraceId // empty' <<<"$OCC")
SID=$(jq -r '.sessionId // empty' <<<"$OCC")
exceptions occurrence $OID --recorded-at $OTS — assert exit 0 and .exception.id == $OID.
- Required-flag enforcement (no live data needed):
exceptions occurrence $OID with no --recorded-at → exit 2 usage_error; --recorded-at notadate → exit 2 invalid_timestamp; endpoints show not-a-uuid --recorded-at $OTS → exit 2 usage_error.
- If
$DT is non-empty: traces show $DT --recorded-at $OTS → exit 0, .nodes is an array. If $SID is non-empty: sessions show $SID --started-at $OTS → exit 0, .session present.
endpoints show / tasks show / ai-traces show need an id of their own type — capture one from a traces show node when available (.nodes[].endpoint.id + .endpoint.recordedAt, etc.); otherwise skip with reason no <type> id captured.
TTY-vs-pipe default
If script is available:
script -q /dev/null ./bin/traceway projects list | head -20
Expect a table. Piping without script should yield JSON. Mark as "not verified" if script is absent.
Subcommand skip lists
Mutating (skip with reason forbidden verb):
exceptions archive, exceptions unarchive, login, logout, profiles use (local mutation), projects use (local mutation of state.json).
Not in the CLI (skip with reason subcommand not in CLI) — kept so the report shows the gap if they ship:
tasks list, sessions list, ai-traces list, traces list, metrics discover.
Observation and reporting
Classify each invocation:
| Result | Classification |
|---|
| exit 0, valid JSON, expected keys present | pass |
| exit 0 but stdout empty when data expected, or JSON invalid / missing keys | fail — schema |
| exit non-zero, clean message, expected error | pass — error case |
| exit non-zero with panic or stack trace | fail — crash |
| exit non-zero on a happy path | fail — unexpected error |
| stderr non-empty on a passing command | warn — noisy |
| > 10s on a list call | warn — slow |
Drive the run from one ephemeral /tmp/*.sh script (not committed). Top of script:
#!/usr/bin/env bash
set -u
unset TRACEWAY_ASSUME_YES
exec </dev/null
LOG=$(mktemp /tmp/traceway-it.XXXXXX.log)
Capture exact invocation, exit code, and first 20 lines of stdout/stderr per probe to $LOG.
End-of-run markdown report:
- Summary:
N pass, M fail, W warn, S skipped + wall-clock.
- Failures table: command, classification, one-line excerpt.
- Warnings table: same shape.
- Skipped table: command + reason.
- Coverage matrix: dimensions exercised per probed command (
— for not exercised).
- Smoke-suite pointer: note whether
just smoke-test was run in this session and its result, so the report stands alone.
- Log path: location of
$LOG.
Do not inline the full log.
What this skill does not do
- It does not duplicate
just smoke-test. Assume that suite's coverage is green; if you suspect regressions there, run smoke separately.
- It does not write or update the CLI's Go test files.
- It does not perform any login, token rotation, profile creation, or credential mutation.
- It does not gate commits or CI.
When to expand
If a new read subcommand ships, add a probe block. If a new mutating subcommand ships, add it to the forbidden list. If a regression pattern shows up that is deterministic and stateless, push it down to test/smoke/*_test.go — not into this skill.