Print elapsed-time status while transcribe engine runs #279
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ci | |
| # Lightweight checks on every push and pull request: format, build, vet, and | |
| # test on Linux, a cross-compile check for the other release platforms, a | |
| # version-stamp ldflags check, a smoke test of the merge → report pipeline on | |
| # the bundled sample, installer syntax and flag-handling checks, full-history | |
| # secret scanning, and a workflow audit. Also runs on `merge_group` so the | |
| # same jobs gate each entry the merge queue builds — the queue tests every PR | |
| # against the exact main it will land on, and its required checks are these | |
| # same contexts, so they MUST trigger here or the queue hangs. | |
| on: | |
| push: | |
| branches: ['**'] | |
| pull_request: | |
| merge_group: | |
| # Cancel superseded runs of the same ref (PRs and non-default branch pushes). | |
| # Never cancel a merge-queue run (each queue entry must complete to merge) or a | |
| # default-branch push; each already has its own ref, so this only affects | |
| # rapidly-superseded PR/branch pushes. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name != 'merge_group' && github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Build + vet + test + pipeline smoke, race-enabled, plus a compile-only | |
| # cross-check for the other release targets. Ubuntu-only for the test run: a | |
| # macos-latest leg aborts the demo/record race binaries with a runner-specific | |
| # dyld "missing LC_UUID" trap (a GitHub image + race-detector issue, not our | |
| # code — the suite is green on a real Mac). Actually running the suite on | |
| # darwin is covered by no gate here; only the binary compiling is (the | |
| # Cross-compile check step below builds ./cmd/testimony, not _test.go | |
| # files). A single job named | |
| # "check" is also the context branch protection requires; a matrix would | |
| # rename it and never report that context. | |
| check: | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Go | |
| uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 | |
| with: | |
| go-version-file: go.mod | |
| # cache: false — the repo has zero dependencies and no go.sum, so | |
| # there is nothing for actions/setup-go's module cache to hold; the | |
| # default (true) restores nothing every run and prints a standing | |
| # "cache folder ... doesn't exist on disk" warning. release.yml | |
| # already sets this for its own reason (cache-poisoning surface on | |
| # a tag-triggered workflow); here it is pure removed overhead. | |
| cache: false | |
| # Any file gofmt lists needs `gofmt -w` and fails the job. | |
| - name: Format (gofmt) | |
| run: | | |
| set -euo pipefail | |
| unformatted="$(gofmt -l .)" | |
| if [ -n "$unformatted" ]; then | |
| echo "gofmt: these files are not formatted (run gofmt -w .):" >&2 | |
| echo "$unformatted" >&2 | |
| exit 1 | |
| fi | |
| - name: Build | |
| run: go build -o testimony ./cmd/testimony | |
| - name: Vet | |
| run: go vet ./... | |
| - name: Test | |
| run: go test -race ./... | |
| # A GOOS-conditional compile break — GOOS-sensitive syscall usage (e.g. | |
| # internal/record's Setpgid or internal/transcribe's Umask) built for a | |
| # platform other than the one it was written and tested against — would | |
| # pass every gate above and only surface in release.yml's | |
| # cross-compile — after a tag is already pushed. Compile-only (this job | |
| # runs no test binaries but its own), CGO_ENABLED=0 to match release.yml's | |
| # build flags; linux/amd64 (the host) is already covered by Build above. | |
| - name: Cross-compile check (darwin, linux/arm64) | |
| run: | | |
| set -euo pipefail | |
| for target in darwin/arm64 darwin/amd64 linux/arm64; do | |
| goos="${target%/*}" | |
| goarch="${target#*/}" | |
| echo "building for $goos/$goarch" | |
| GOOS="$goos" GOARCH="$goarch" CGO_ENABLED=0 go build -o /dev/null ./cmd/testimony | |
| done | |
| # The Go linker silently ignores -X for a symbol that does not exist — | |
| # rename or move internal/cli.Version, or the module path, and every | |
| # gate above stays green while release.yml's -ldflags stamp silently | |
| # stops taking effect, surfacing only after a tag is already pushed (the | |
| # same "surfaces only after the tag is public" class the cross-compile | |
| # check above exists to close, applied to the version stamp instead of | |
| # a GOOS-conditional break). Built and run on the host (linux/amd64) with | |
| # the identical -X path release.yml uses, so a broken symbol path fails | |
| # here first. | |
| - name: Version-stamp ldflags check | |
| run: | | |
| set -euo pipefail | |
| go build -o /tmp/testimony-stamp-check \ | |
| -ldflags "-X github.com/REPPL/Testimony/internal/cli.Version=vTEST" \ | |
| ./cmd/testimony | |
| got="$(/tmp/testimony-stamp-check version)" | |
| if [ "$got" != "testimony vTEST" ]; then | |
| echo "version-stamp ldflags had no effect: got \"$got\", want \"testimony vTEST\" — internal/cli.Version or the module path may have moved" >&2 | |
| exit 1 | |
| fi | |
| # The full pipeline against the bundled sample session: merge must produce | |
| # a timeline, report must render it, and both outputs must be non-empty. | |
| # Ubuntu-only, like the rest of this job — it also proves the freshly built | |
| # binary actually runs the pipeline, not merely that it compiles. | |
| - name: Pipeline smoke test | |
| run: | | |
| set -euo pipefail | |
| ./testimony merge -session examples/sample-session | |
| ./testimony report -session examples/sample-session | |
| test -s examples/sample-session/timeline.jsonl | |
| test -s examples/sample-session/report.md | |
| grep -q "## Timeline" examples/sample-session/report.md | |
| grep -q "save button" examples/sample-session/report.md | |
| # The bundled findings.jsonl renders the Findings section with the | |
| # confirmed save-feedback bug (F-001). | |
| grep -q "## Findings" examples/sample-session/report.md | |
| grep -q "### Confirmed (1)" examples/sample-session/report.md | |
| grep -q "\*\*F-001\*\* bug" examples/sample-session/report.md | |
| # The event half of the pipeline: every assertion above is satisfiable | |
| # from the transcript and findings alone (verified: deleting | |
| # interactions.jsonl left them all green) — the header counts assertion | |
| # below is what catches that, since the save-btn selector also renders | |
| # via findings.jsonl's Findings section regardless of events. The exact | |
| # counts pin the bundled fixture; extending the sample session means | |
| # updating them here and in release.yml. | |
| grep -q "\*\*Utterances:\*\* 10 · \*\*Events:\*\* 10" examples/sample-session/report.md | |
| grep -q "data-testid=save-btn" examples/sample-session/report.md | |
| # The counts line above catches events going missing from the merge, | |
| # but not a windowing/attachment regression: report's header counts are | |
| # raw entry counts, computed before the join, so they stay "10 · 10" | |
| # even when every event is detached from the speech it accompanies | |
| # (verified: -window set absurdly negative still passes every assertion | |
| # above). An indented bullet is report's join marker (an unattached | |
| # event renders as a standalone "- ", flush with the left margin), so | |
| # assert one specifically: the first Save click, joined under its | |
| # utterance. | |
| grep -q '^ - \[00:19\] click `\[data-testid=save-btn\]`' examples/sample-session/report.md | |
| # install.sh is served live from main as the documented install path, so | |
| # a merged syntax error breaks `curl | sh` for every new user with no | |
| # gate having run. Parse it with both shells an operator realistically | |
| # pipes it into. | |
| - name: Installer syntax | |
| run: | | |
| set -euo pipefail | |
| sh -n install.sh | |
| bash -n install.sh | |
| # A syntax check alone never runs a single line of install.sh, so a | |
| # behavioural regression in --help or the flag-error paths (CHANGELOG's | |
| # "Installer" entries) would ship to `curl | sh` users with no gate having | |
| # exercised it. These six paths all return before any network access or | |
| # write, so they run safely offline, unlike a full install. Each failing | |
| # case is wrapped in `{ ...; || true; }` so its deliberate non-zero exit | |
| # does not trip pipefail before grep gets to check the message — a plain | |
| # `(cmd; true)` subshell does not do this under `set -e`: -e is inherited | |
| # into the subshell, so `cmd` failing aborts it right there and `true` | |
| # never runs, leaving the subshell's own exit status non-zero after all. | |
| - name: Installer flag handling | |
| run: | | |
| set -euo pipefail | |
| sh install.sh --help | grep -q "Flags:" | |
| { sh install.sh --dir || true; } 2>&1 | grep -q "needs a value" | |
| { sh install.sh --version || true; } 2>&1 | grep -q "needs a value" | |
| { sh install.sh --dir "" || true; } 2>&1 | grep -q "needs a value" | |
| { sh install.sh --version "" || true; } 2>&1 | grep -q "needs a value" | |
| { sh install.sh --bogus || true; } 2>&1 | grep -q "unknown flag" | |
| # Secrets never land in history: full-history scan on every push/PR. Run as a | |
| # pinned, checksum-verified CLI (no PR-comment / SARIF GitHub-API integration | |
| # and no marketplace-action licence caveat), so it is self-contained. A | |
| # non-zero exit means gitleaks found a secret. | |
| gitleaks: | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| env: | |
| GITLEAKS_VERSION: 8.24.3 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Install gitleaks (pinned, checksum-verified) | |
| run: | | |
| set -euo pipefail | |
| cd "$RUNNER_TEMP" | |
| base="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" | |
| url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}" | |
| curl -sSLf -o "$base" "$url/$base" | |
| curl -sSLf -o checksums.txt "$url/gitleaks_${GITLEAKS_VERSION}_checksums.txt" | |
| grep " $base\$" checksums.txt | sha256sum -c - | |
| tar -xzf "$base" gitleaks | |
| sudo install gitleaks /usr/local/bin/gitleaks | |
| gitleaks version | |
| - name: Scan full history | |
| run: gitleaks git --redact --no-banner . | |
| # Audit the workflows themselves (pwn requests, template injection, cache | |
| # poisoning, unpinned uses). The action runs zizmor and gates on its findings | |
| # (a non-zero exit means a finding); SARIF upload to Code Scanning is off — see | |
| # the advanced-security note below. | |
| zizmor: | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 | |
| with: | |
| # advanced-security: false — Code Scanning is not enabled on this repo, | |
| # so a SARIF upload would fail the job even with zero findings. The | |
| # action still runs zizmor and fails on any finding above its threshold; | |
| # findings surface in the job log. Flip to true (and add | |
| # security-events: write) if Code Scanning default setup is enabled later. | |
| advanced-security: false |