{"id":19331340,"url":"https://github.com/andriykalashnykov/flight-path","last_synced_at":"2026-05-07T00:32:23.512Z","repository":{"id":163739943,"uuid":"614372304","full_name":"AndriyKalashnykov/flight-path","owner":"AndriyKalashnykov","description":"REST API server to determine the flight path of a person","archived":false,"fork":false,"pushed_at":"2025-09-07T23:59:48.000Z","size":367,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-12T00:09:22.953Z","etag":null,"topics":["echo","echo-framework","github-actions","github-ci","go","golang","newman","postman","postman-test","rest","rest-api","restful-api","swagger","swagger-ui","swaggo"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndriyKalashnykov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-03-15T13:06:36.000Z","updated_at":"2025-09-07T23:59:50.000Z","dependencies_parsed_at":"2023-10-12T14:17:59.020Z","dependency_job_id":"a375edb9-4993-47d2-b103-2240ae60f85e","html_url":"https://github.com/AndriyKalashnykov/flight-path","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/AndriyKalashnykov/flight-path","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndriyKalashnykov%2Fflight-path","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndriyKalashnykov%2Fflight-path/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndriyKalashnykov%2Fflight-path/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndriyKalashnykov%2Fflight-path/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndriyKalashnykov","download_url":"https://codeload.github.com/AndriyKalashnykov/flight-path/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndriyKalashnykov%2Fflight-path/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275364759,"owners_count":25451517,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-16T02:00:10.229Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["echo","echo-framework","github-actions","github-ci","go","golang","newman","postman","postman-test","rest","rest-api","restful-api","swagger","swagger-ui","swaggo"],"created_at":"2024-11-10T02:39:45.373Z","updated_at":"2026-05-07T00:32:23.498Z","avatar_url":"https://github.com/AndriyKalashnykov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/AndriyKalashnykov/flight-path/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/AndriyKalashnykov/flight-path/actions/workflows/ci.yml)\n[![Hits](https://hits.sh/github.com/AndriyKalashnykov/flight-path.svg?view=today-total\u0026style=plastic)](https://hits.sh/github.com/AndriyKalashnykov/flight-path/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)\n[![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://app.renovatebot.com/dashboard#github/AndriyKalashnykov/flight-path)\n\n# Go REST API to reconstruct flight paths from unordered segments\n\nA Go REST API microservice that calculates flight paths from unordered flight segments. Given a list of `[source, destination]` pairs, it determines the complete path from origin (the airport with no incoming segment) to terminus (the airport with no outgoing segment).\n\n## Overview\n\n```mermaid\nC4Context\n    title System Context — flight-path\n    Person(client, \"API Client\", \"cURL, Postman, Newman, browser\")\n    System(flightpath, \"flight-path\", \"Go REST API that reconstructs full itinerary from unordered flight segments\")\n    Rel(client, flightpath, \"POST /calculate\", \"HTTPS/JSON\")\n    UpdateLayoutConfig($c4ShapeInRow=\"2\", $showLegend=\"true\")\n```\n\nSee [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) for Container, request-flow sequence, and CI/CD pipeline diagrams.\n\n## Tech Stack\n\n| Component | Technology | Rationale |\n|-----------|------------|-----------|\n| Language | Go 1.26.2 (from `go.mod`) | Statically compiled binary, strong stdlib HTTP, goroutine concurrency |\n| Framework | Echo v5.1.0 | Lightweight router with built-in JSON binding, middleware stack, Swagger integration |\n| API Docs | Swagger (swaggo/swag v2) | Auto-generated OpenAPI spec from Go annotations |\n| Testing | go test (unit, bench, fuzz), Newman/Postman (E2E) | Table-driven unit tests + black-box API tests against the built binary |\n| Linting | golangci-lint v2.11.4, hadolint, actionlint, shellcheck, mermaid-cli (via Docker) | Meta-linter + Dockerfile + workflows + shell + diagrams |\n| Container | Docker (multi-stage Alpine) | Small image, reproducible build, scratch-like runtime |\n| CI/CD | GitHub Actions + GoReleaser | Tag-gated release pipeline with cosign keyless signing |\n| Dependencies | Renovate | Auto-updates with platform automerge and security fast-track |\n\n## Quick Start\n\n```bash\nmake deps      # install dev tools (golangci-lint, gosec, swag, pnpm, newman, etc.)\nmake build     # generate Swagger docs + compile binary\nmake test      # run unit + handler tests\nmake run       # build and start the server\n# Open http://localhost:8080/swagger/index.html\n```\n\n## Prerequisites\n\n| Tool | Version | Purpose |\n|------|---------|---------|\n| [Go](https://go.dev/dl/) | 1.26.2 (see `go.mod`) | Language runtime and compiler |\n| [mise](https://mise.jdx.dev/) | latest | Toolchain manager — reads `.mise.toml` to install pinned Go + Node |\n| [GNU Make](https://www.gnu.org/software/make/) | 3.81+ | Build orchestration |\n| [Git](https://git-scm.com/) | 2.0+ | Version control |\n| [Docker](https://www.docker.com/) | latest | Container builds and testing |\n| [Node.js](https://nodejs.org/) | 24 (from `.nvmrc` / `.mise.toml`) | Newman E2E tests *(installed via mise)* |\n| [pnpm](https://pnpm.io/) | pinned in `test/package.json` | Newman package manager *(enabled via corepack)* |\n| [curl](https://curl.se/) | any | Ad-hoc API calls (`make test-case-*`) |\n\nInstall all required dev tools:\n\n```bash\nmake deps\n```\n\n## Architecture\n\nLayered Go microservice with a single HTTP endpoint over an in-memory algorithm — no database, no external services.\n\n| Layer | Location | Responsibility |\n|-------|----------|----------------|\n| Entry point | `main.go` | Load `.env`, construct `App`, start Echo server on `SERVER_PORT` |\n| Bootstrap | `internal/app/` | Wire Echo instance, middleware stack (RequestLogger, Recover, CORS, Secure, Cache-Control/CORP custom headers), routes |\n| Routes | `internal/routes/` | Register method-verb mappings against a `*handlers.Handler` |\n| Handlers | `internal/handlers/` | Bind request, validate, delegate to `FindItinerary`, return JSON |\n| Algorithm | `internal/handlers/api.go` | `FindItinerary` — O(n) reconstruction: build source/destination sets, find unique start (in-degree 0) and end (out-degree 0) airports |\n| Public types | `pkg/api/` | `Flight{Start,End}` struct exported for consumers |\n\n```mermaid\nflowchart LR\n    client[\"API Client\u003cbr/\u003e(curl / Postman / browser)\"]\n    subgraph server[\"flight-path (Echo v5)\"]\n        mw[\"Middleware stack\u003cbr/\u003eRequestLogger · Recover · CORS · Secure\u003cbr/\u003eCache-Control · CORP\"]\n        routes[\"Routes\u003cbr/\u003ePOST /calculate\u003cbr/\u003eGET /\u003cbr/\u003eGET /swagger/*\"]\n        handlers[\"Handlers\u003cbr/\u003eFlightCalculate\u003cbr/\u003eServerHealthCheck\"]\n        algo[\"FindItinerary()\u003cbr/\u003eO(n) in-memory graph walk\"]\n    end\n    client --\u003e mw --\u003e routes --\u003e handlers --\u003e algo\n```\n\nSee [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) for Container, request-flow sequence, and CI/CD pipeline diagrams.\n\n## API\n\n- **POST /calculate** — accepts `[][]string` flight segments, returns `[]string` (start and end airports)\n- **GET /** — health check\n- **GET /swagger/*** — Swagger UI ([http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html))\n\nAuto-generated OpenAPI spec: [`docs/swagger.json`](./docs/swagger.json)\n\n![Swagger API documentation](./img/swagger-api-doc.jpg)\n\n## Security \u0026 Code Quality\n\n### SAST (Static Application Security Testing)\n\n| Tool | Command | What it does |\n|------|---------|-------------|\n| [gosec](https://github.com/securego/gosec) | `make sec` | Go-specific security scanner (injection, crypto, permissions) |\n| [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) | `make vulncheck` | Checks dependencies against the Go vulnerability database |\n| [gitleaks](https://github.com/gitleaks/gitleaks) | `make secrets` | Scans source code and git history for hardcoded secrets |\n\n### DAST (Dynamic Application Security Testing)\n\n| Tool | Where | What it does |\n|------|-------|-------------|\n| [OWASP ZAP](https://github.com/zaproxy/zaproxy) | CI only | API security scan using Swagger/OpenAPI spec |\n\n### Linting\n\n| Tool | Command | What it does |\n|------|---------|-------------|\n| [golangci-lint](https://github.com/golangci/golangci-lint) | `make lint` | Meta-linter with comprehensive rule set (configured via `.golangci.yml`) |\n| [hadolint](https://github.com/hadolint/hadolint) | `make lint` | Dockerfile linter |\n| [actionlint](https://github.com/rhysd/actionlint) | `make lint-ci` | Lints GitHub Actions workflow files (uses shellcheck internally) |\n| [shellcheck](https://github.com/koalaman/shellcheck) | `make lint-ci` | Validates shell scripts inside workflow `run:` steps |\n| [mermaid-cli](https://github.com/mermaid-js/mermaid-cli) | `make mermaid-lint` | Validates Mermaid diagrams in markdown files against GitHub's renderer |\n\n### Container Security\n\n| Tool | Where | What it does |\n|------|-------|-------------|\n| [Trivy](https://github.com/aquasecurity/trivy) | CI + local (`make trivy-fs`, `make image-scan`) | Scans Docker images and filesystem for CVEs |\n\n### Testing\n\n| Tool | Command | What it does |\n|------|---------|-------------|\n| go test | `make test` | Unit and handler tests (table-driven) |\n| go test -bench | `make bench` | Benchmark tests for critical paths |\n| go test -fuzz | `make fuzz` | Fuzz testing for FindItinerary algorithm |\n| [Newman](https://github.com/postmanlabs/newman) | `make e2e` | Postman/Newman end-to-end API tests |\n\n### End-to-end tests (Postman/Newman)\n\nThe Postman collection ([JSON](./test/FlightPath.postman_collection.json)) executes 18 test cases:\n\n- **HealthCheck** — `GET /` returns `{\"data\": \"...\"}`\n- **UseCase01–03** — happy paths matching `test-case-one`, `test-case-two`, `test-case-three` (1, 2, 4 segments)\n- **UseCase04_EmptyBody** — `[]` → 400 \"empty segments\"\n- **UseCase05_MalformedJSON** — `not valid json` → 400 \"parse\"\n- **UseCase06_IncompleteSegment** — `[[\"SFO\"]]` → 400 \"source and destination\"\n- **UseCase07_ExtraItemsInSegmentIgnored** — `[[\"SFO\",\"EWR\",\"JFK\"]]` → 200, first two elements used\n- **UseCase08_TenSegmentChain** — 10 scrambled segments resolving LAX→SFO, exercises the algorithm on longer inputs\n- **UseCase09_ObjectRoot** — `{\"foo\":\"bar\"}` → 400 \"parse\" (wrong root JSON type)\n- **UseCase10_SecondSegmentIncomplete** — `[[\"SFO\",\"EWR\"],[\"JFK\"]]` → 400 with `Index: 1` pointing at the offending segment\n- **HealthCheck_SecurityHeaders** — asserts HSTS / X-Content-Type-Options / X-Frame-Options / CSP on `/`\n- **HealthCheck_CORS** — asserts default `Access-Control-Allow-Origin: *`\n- **Swagger_UI** — asserts `GET /swagger/index.html` returns 200 HTML\n- **UseCase11_WrongMethod** — `GET /calculate` → 405\n- **UseCase12_UnknownRoute** — `GET /does-not-exist` → 404\n- **UseCase13_LargeChain** — 100-segment chain exercising the algorithm on wide inputs\n- **UseCase14_WrongContentType** — `text/plain` body → 400 \"content-type\"\n\nUses hybrid validation: Ajv JSON Schema validation for `/calculate` response structure (global schemas defined at collection level) and Chai assertions for exact business values. The collection-level schema check skips non-`/calculate` requests so the HealthCheck assertions run in isolation.\n\n![Postman/Newman end-to-end tests](./img/postman-newman.jpg)\n\n## Available Make Targets\n\nRun `make help` to see all available targets.\n\n### Build \u0026 Run\n\n| Target | Description |\n|--------|-------------|\n| `make build` | Build REST API server's binary |\n| `make run` | Run REST API locally |\n| `make api-docs` | Generate Swagger API documentation from Go annotations |\n| `make clean` | Remove build artifacts and test cache |\n| `make update` | Update Go dependencies to latest versions and run `go mod tidy` |\n\n### Testing\n\n| Target | Description |\n|--------|-------------|\n| `make test` | Run unit + handler tests with `-race` |\n| `make integration-test` | Run integration tests (full HTTP stack + middleware; `//go:build integration`) |\n| `make fuzz` | Run fuzz tests for 30 seconds |\n| `make bench` | Run bench tests |\n| `make bench-save` | Save benchmark results to file |\n| `make bench-compare` | Compare two benchmark files (auto-discovers latest two, or: `make bench-compare OLD=file1.txt NEW=file2.txt`) |\n| `make coverage` | Run tests with coverage report |\n| `make coverage-check` | Verify coverage meets 80% threshold |\n| `make e2e` | Self-contained: build + start server + run Newman + stop server |\n| `make e2e-quick` | Run Postman/Newman tests against an already-running server |\n\n### Code Quality\n\n| Target | Description |\n|--------|-------------|\n| `make format` | Format Go code (rewrites in place; for dev use) |\n| `make format-check` | Verify Go code is gofmt-clean (CI gate; non-mutating, exits non-zero on diff) |\n| `make lint` | Run golangci-lint and hadolint (comprehensive linting via .golangci.yml) |\n| `make sec` | Run gosec security scanner |\n| `make vulncheck` | Run Go vulnerability check on dependencies |\n| `make secrets` | Scan for hardcoded secrets in source code and git history |\n| `make lint-ci` | Lint GitHub Actions workflow files |\n| `make mermaid-lint` | Validate Mermaid diagrams in markdown files |\n| `make release-check` | Validate `.goreleaser.yml` syntax and config via `goreleaser check` |\n| `make static-check` | Run code static check (format-check + lint-ci + lint + sec + vulncheck + secrets + trivy-fs + mermaid-lint + release-check) |\n\n### Docker\n\n| Target | Description |\n|--------|-------------|\n| `make image-build` | Build Docker image for local testing |\n| `make image-run` | Run Docker container locally (detached; use `image-stop` to tear down) |\n| `make image-stop` | Stop the locally running Docker container |\n| `make image-push` | Push Docker image to GHCR (requires `GH_ACCESS_TOKEN`; `GHCR_USER` defaults to `git config user.name`) |\n| `make image-smoke-test` | Smoke-test a pre-built Docker container (no rebuild) |\n| `make image-structure-test` | Validate Dockerfile metadata + binary properties via container-structure-test |\n| `make image-test` | Build, smoke-test, and structure-test Docker container |\n| `make image-scan` | Build Docker image and run Trivy scan (requires trivy) |\n| `make trivy-fs` | Run Trivy filesystem vulnerability scan (requires trivy) |\n| `make trivy-image` | Run Trivy image vulnerability scan (requires trivy) |\n\n### CI\n\n| Target | Description |\n|--------|-------------|\n| `make ci` | Run full CI pipeline locally (deps + static-check + test + integration-test + coverage-check + build + fuzz + deps-prune-check) |\n| `make ci-run` | Run GitHub Actions workflow locally using [act](https://github.com/nektos/act) |\n| `make check` | Run pre-commit checklist (alias for `make ci`) |\n\n### Utilities\n\n| Target | Description |\n|--------|-------------|\n| `make help` | List available tasks |\n| `make deps` | Install dev tools — `mise install` reads `.mise.toml` and provisions Go, Node, and every quality/security tool (golangci-lint, gosec, govulncheck, gitleaks, actionlint, shellcheck, hadolint, trivy, act, goreleaser). swag + benchstat stay Go-installed; newman via pnpm + corepack |\n| `make deps-check` | Show required Go version, mise status, and tool status |\n| `make release` | Run full CI pipeline then tag and push a new release |\n| `make open-swagger` | Open browser with Swagger docs pointing to localhost |\n| `make renovate-validate` | Validate Renovate configuration |\n| `make deps-prune` | Remove unused Go module dependencies |\n| `make deps-prune-check` | Verify no prunable dependencies (CI gate) |\n| `make test-case-one` | Test case #1 `[[\"SFO\", \"EWR\"]]` |\n| `make test-case-two` | Test case #2 `[[\"ATL\", \"EWR\"], [\"SFO\", \"ATL\"]]` |\n| `make test-case-three` | Test case #3 `[[\"IND\", \"EWR\"], [\"SFO\", \"ATL\"], [\"GSO\", \"IND\"], [\"ATL\", \"GSO\"]]` |\n\n## CI/CD\n\nGitHub Actions runs on every push to `main`, tags `v*`, and pull requests. All jobs live in a single workflow file (`.github/workflows/ci.yml`). The workflow always triggers; a `changes` detector job (`dorny/paths-filter`) gates every heavy job on whether the push touches code — doc-only PRs only run `changes` (~10s) and `ci-pass`. The release-side `goreleaser` (tag-only) and `docker` (every push, push/sign tag-gated) are serialized via `needs:` so a tag either produces both the GitHub Release AND the GHCR image, or neither.\n\n| Job | Triggers | Steps |\n|-----|----------|-------|\n| **changes** | push, PR, tags | `dorny/paths-filter` — emits `code` output. Filter: `!(**.md|docs/**|specs/**|LICENSE|.gitignore|.claudeignore|.claude/**|benchmarks/**|**.png|**.jpg|**.gif|**.svg)` plus `CLAUDE.md` re-include. |\n| **static-check** | code changes | `make static-check` (format-check + lint-ci + lint + sec + vulncheck + secrets + trivy-fs + mermaid-lint + release-check) |\n| **build** | code changes, after static-check | Build binary, upload artifact |\n| **test** | code changes, after static-check | Coverage threshold check (80%+), fuzz tests |\n| **integration-test** | code changes, after static-check | Full HTTP stack + middleware tests (`//go:build integration`) |\n| **e2e** | code changes, after build + test | Download binary (or rebuild fallback), run server, Newman/Postman E2E tests. Runs on every push AND under `act` (no `vars.ACT` guard) — the fallback path rebuilds the binary when cross-job artifact download fails. |\n| **dast** | code changes, after build + test | Run server, OWASP ZAP API security scan (skipped under `act`) |\n| **goreleaser** | tag push only, after all upstream | GoReleaser build, GitHub release (binaries, archives, checksums, changelog). Anchor of the multi-artifact release — `docker` is serialized after this so a tag either produces both artifacts or none. |\n| **docker** | code changes, after static-check + build + test; serialized after goreleaser on tag push | Single-arch build + Trivy image scan (CRITICAL/HIGH blocking) + `make image-smoke-test` + multi-arch build. On `v*.*.*` tag pushes, additionally logs in to GHCR, pushes multi-arch (clean image index, Pattern A), and cosign-signs by digest. On non-tag pushes the login/push/sign steps are skipped — the job still runs end-to-end to catch Dockerfile and multi-arch build regressions on the commit that introduced them, not on release day. |\n| **ci-pass** | `if: always()`, needs all | Single branch-protection gate that fails if any upstream job failed. Skipped jobs (changes.code=false on doc-only PRs, goreleaser on non-tag pushes, dast under act) are `result: 'skipped'`, treated as non-failure — ci-pass passes correctly. |\n\n### Required Secrets and Variables\n\n| Name | Type | Used by | How to obtain |\n|------|------|---------|---------------|\n| `CLAUDE_CONFIG_TOKEN` | Secret | `claude.yml`, `claude-ci-fix.yml` | PAT with `contents: read` for [`AndriyKalashnykov/claude-config`](https://github.com/AndriyKalashnykov/claude-config) — allows workflows to check out shared Claude configuration |\n| `ANTHROPIC_API_KEY` | Secret | `claude.yml`, `claude-ci-fix.yml` | [console.anthropic.com](https://console.anthropic.com/) API key — powers the Claude Code action |\n\nSet secrets via **Settings \u003e Secrets and variables \u003e Actions \u003e New repository secret**.\nSet variables via **Settings \u003e Secrets and variables \u003e Actions \u003e Variables tab \u003e New repository variable**.\n\n**Local-only variables (act):** `ACT=true` is injected automatically by `make ci-run` (via `--var ACT=true`) to guard the `dast` job, which needs Docker-in-Docker for OWASP ZAP and doesn't run cleanly under act. Do **not** set `ACT` on GitHub Actions runners.\n\n### Pre-push image hardening\n\nThe `docker` job runs the following gates on **every push**. Gates 1–3 run unconditionally (catching Dockerfile and Trivy regressions on every commit). Gate 4 (multi-arch build) runs on every push but only pushes to GHCR on `v*.*.*` tag pushes. Gate 5 (cosign signing) is tag-only. Any failure blocks the release.\n\n| # | Gate | Catches | Tool | When |\n|---|---|---|---|---|\n| 1 | Build local single-arch image | Build regressions on the runner architecture | `docker/build-push-action` with `load: true` | every push |\n| 2 | **Trivy image scan** (CRITICAL/HIGH blocking) | CVEs in base image, OS packages, build layers; secrets; misconfigs | `aquasecurity/trivy-action` with `image-ref:` | every push |\n| 3 | **Smoke test** | Image boots, health endpoint responds, `/calculate` returns correct result | `make image-smoke-test` | every push |\n| 3b | **Container structure test** | Dockerfile metadata invariants (binary path, USER, entrypoint, /etc/passwd content) | `make image-structure-test` | every push |\n| 4 | Multi-arch build + conditional push (clean image index) | Multi-arch build regressions (linux/arm64 cross-compile issues); on tags, publishes with `provenance: false` + `sbom: false` so the GHCR \"OS / Arch\" tab renders correctly | `docker/build-push-action` with `push: ${{ startsWith(github.ref, 'refs/tags/') }}` | every push (build); tag only (push) |\n| 5 | **Cosign keyless OIDC signing** | Sigstore signature on the manifest digest (no long-lived keys) — the supply-chain verification primitive for this image | `sigstore/cosign-installer` + `cosign sign` | tag only |\n\nInspect the published multi-arch manifest:\n\n```bash\ndocker buildx imagetools inspect ghcr.io/andriykalashnykov/flight-path:\u003ctag\u003e\n```\n\nExpect `linux/amd64` and `linux/arm64` platform entries with no `unknown/unknown` rows.\n\nVerify a published image's cosign signature:\n\n```bash\ncosign verify ghcr.io/andriykalashnykov/flight-path:\u003ctag\u003e \\\n  --certificate-identity-regexp 'https://github\\.com/AndriyKalashnykov/flight-path/\\.github/workflows/ci\\.yml@refs/tags/v.*' \\\n  --certificate-oidc-issuer https://token.actions.githubusercontent.com\n```\n\n### Companion workflows\n\n| Workflow | Trigger | Purpose |\n|----------|---------|---------|\n| [`claude.yml`](./.github/workflows/claude.yml) | `@claude` mention from trusted author; non-draft PR open/sync | Interactive Claude Code responder + automated PR review |\n| [`claude-ci-fix.yml`](./.github/workflows/claude-ci-fix.yml) | CI failure on same-repo PR branch | Attempt automated fix with anti-recursion guards (bot-author + label) |\n| [`auto-merge.yml`](./.github/workflows/auto-merge.yml) | Renovate PR opened or `ci-pass` succeeds | Enable GitHub native auto-merge on Renovate PRs once required checks pass |\n| [`nightly-fuzz.yml`](./.github/workflows/nightly-fuzz.yml) | Daily at 03:17 UTC + manual dispatch | Run `FuzzFindItinerary` for 10 min (vs the 30 s in `ci.yml`); accumulates the corpus across runs and opens a tracking issue on failure |\n| [`cleanup-runs.yml`](./.github/workflows/cleanup-runs.yml) | Sundays at 00:00 UTC | Delete old workflow runs (retain 7 days, min 5) and prune merged-branch caches |\n\n[Renovate](https://docs.renovatebot.com/) keeps dependencies up to date with platform automerge enabled.\n\n## Contributing\n\nContributions welcome — open an issue or pull request. Run `make check` locally before pushing.\n\n## License\n\n[MIT](./LICENSE) © Andriy Kalashnykov\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriykalashnykov%2Fflight-path","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandriykalashnykov%2Fflight-path","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandriykalashnykov%2Fflight-path/lists"}