From 3763d9422cca7321511ccff87c52ead2fbb99f57 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 12:12:04 +0530 Subject: [PATCH 1/7] chore: de-submodule ecosystem to a workspace (go.work + sibling repos) Replace git submodules under external/ with a workspace go.work that resolves the GrayCodeAI ecosystem repos as siblings (../): - go.work: use . + ../ instead of replace => ./external/ - remove external/* submodules and .gitmodules - Makefile: setup generates the workspace go.work; add release-parity + sync targets replacing the submodule sync targets - scripts: rewrite release-parity to compare go.mod versions vs sibling HEAD; drop 3 obsolete submodule sync scripts; update boundary/verify scripts - cmd/compat-test: drift check + tests read sibling repos - CI/actions: clone siblings into the workspace parent (no submodule init) - Dockerfiles: build in module mode against pinned go.mod versions - docs/AGENTS.md/README: document the workspace model - add docs/architecture/ecosystem-design.md --- .github/actions/checkout-eyrie/action.yml | 61 +++-------- .github/actions/setup-deps/action.yml | 28 ++--- .github/workflows/ci.yml | 22 ++-- .github/workflows/compatibility-matrix.yml | 4 - .github/workflows/daemon-image.yml | 3 - .github/workflows/docker.yml | 8 -- .github/workflows/release.yml | 27 +---- .gitmodules | 24 ----- AGENTS.md | 17 ++- Dockerfile | 20 ++-- Dockerfile.daemon | 4 - Makefile | 64 +++-------- README.md | 23 ++-- cmd/compat-test/drift.go | 18 ++-- cmd/compat-test/drift_test.go | 34 +++--- cmd/compat-test/main.go | 4 +- docs/DYNAMIC-MODELS.md | 4 +- docs/architecture.md | 4 +- docs/architecture/ecosystem-architecture.md | 24 ++--- docs/architecture/ecosystem-design.md | 112 ++++++++++++++++++++ docs/architecture/plan.md | 8 +- docs/architecture/spec.md | 2 +- docs/architecture/tasks.md | 2 +- docs/troubleshooting-guide.md | 6 +- external/eyrie | 1 - external/hawk-core-contracts | 1 - external/hawk-mcpkit | 1 - external/inspect | 1 - external/sight | 1 - external/tok | 1 - external/trace | 1 - external/yaad | 1 - go.work | 21 ++-- scripts/check-ecosystem-boundaries.sh | 7 -- scripts/check-submodule-release-parity.sh | 39 ++++--- scripts/check-support-repo-coupling.sh | 1 - scripts/sync-clone.sh | 29 ----- scripts/sync-external.sh | 69 ------------ scripts/sync-submodule-versions.sh | 86 --------------- scripts/verify-milestone.sh | 10 +- 40 files changed, 287 insertions(+), 506 deletions(-) delete mode 100644 .gitmodules create mode 100644 docs/architecture/ecosystem-design.md delete mode 160000 external/eyrie delete mode 160000 external/hawk-core-contracts delete mode 160000 external/hawk-mcpkit delete mode 160000 external/inspect delete mode 160000 external/sight delete mode 160000 external/tok delete mode 160000 external/trace delete mode 160000 external/yaad delete mode 100755 scripts/sync-clone.sh delete mode 100755 scripts/sync-external.sh delete mode 100755 scripts/sync-submodule-versions.sh diff --git a/.github/actions/checkout-eyrie/action.yml b/.github/actions/checkout-eyrie/action.yml index 198388be..0f4be0b5 100644 --- a/.github/actions/checkout-eyrie/action.yml +++ b/.github/actions/checkout-eyrie/action.yml @@ -1,68 +1,37 @@ name: Checkout ecosystem -description: Clone graycode-eco ecosystem repos into hawk/external for hawk go.work +description: Clone graycode-eco sibling repos into the workspace parent for hawk's go.work inputs: ref: - description: Git ref to checkout only when allow_branch_fallback is true + description: Git ref to checkout (falls back to main) required: false default: main - allow_branch_fallback: - description: > - When true, clone a branch head if the Gitlink is missing (dev-only escape hatch). - Default false — missing or unreachable pins fail the job so releases never - silently track main. - required: false - default: "false" runs: using: composite steps: - - name: Clone ecosystem repos + - name: Clone sibling repos shell: bash env: - # Passed via env (not ${{ }} interpolation into the script) so an - # attacker-controlled ref (e.g. a fork branch name) cannot inject shell. + # Passed via env (not ${{ }} interpolation) so an attacker-controlled + # ref (e.g. a fork branch name) cannot inject shell. INPUT_REF: ${{ inputs.ref }} - ALLOW_BRANCH_FALLBACK: ${{ inputs.allow_branch_fallback }} run: | set -euo pipefail - mkdir -p "${GITHUB_WORKSPACE}/external" - for repo in hawk-core-contracts eyrie inspect sight tok trace yaad; do - dest="${GITHUB_WORKSPACE}/external/${repo}" + # hawk's committed go.work references ../, so the siblings must + # be cloned into the parent of the checked-out workspace. + ws_parent="$(cd "${GITHUB_WORKSPACE}/.." && pwd)" + for repo in hawk-core-contracts eyrie inspect sight tok trace yaad hawk-mcpkit; do + dest="${ws_parent}/${repo}" if [ -d "$dest/.git" ]; then echo "$repo already present at $dest" continue fi - commit=$(git ls-tree HEAD "external/${repo}" | awk '{print $3}' || true) - if [ -n "$commit" ]; then - echo "Cloning $repo at submodule commit $commit" - # Full clone so the pinned commit is reachable even after - # the dependency repo's main has been rewritten past it - # (e.g. by a squash-merge). A depth-1 clone can't check - # out older commits and fails with "unable to read tree". - git clone "https://github.com/GrayCodeAI/${repo}.git" "$dest" - if ! (cd "$dest" && git checkout --quiet "$commit"); then - echo "::error::Pinned submodule commit $commit is not reachable in $repo" - echo "Refusing to test an unpinned branch head for a pinned Hawk commit." - exit 1 - fi + if git ls-remote --heads "https://github.com/GrayCodeAI/${repo}.git" "$INPUT_REF" | grep -q .; then + echo "Cloning $repo at branch $INPUT_REF" + git clone --depth=1 --branch "$INPUT_REF" "https://github.com/GrayCodeAI/${repo}.git" "$dest" else - if [ "${ALLOW_BRANCH_FALLBACK}" != "true" ]; then - echo "::error::Missing Gitlink for external/${repo} at HEAD" - echo "Hawk requires a pinned submodule commit for every engine." - echo "Record the pin with: git submodule update --init external/${repo}" - echo "and commit the Gitlink. Branch-head fallback is disabled by default" - echo "(set allow_branch_fallback=true only for local experiments)." - exit 1 - fi - ref="$INPUT_REF" - # Optional escape hatch: fall back to main if the branch doesn't exist. - if ! git ls-remote --heads "https://github.com/GrayCodeAI/${repo}.git" "$ref" | grep -q .; then - echo "Branch '$ref' not found on $repo, falling back to main" - ref="main" - fi - echo "::warning::Cloning $repo at branch head '$ref' (no Gitlink; allow_branch_fallback=true)" - git clone --depth=1 --branch "$ref" \ - "https://github.com/GrayCodeAI/${repo}.git" "$dest" + echo "Branch '$INPUT_REF' not found on $repo, falling back to main" + git clone --depth=1 --branch main "https://github.com/GrayCodeAI/${repo}.git" "$dest" fi done diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml index 7b6522ee..b9e6ed83 100644 --- a/.github/actions/setup-deps/action.yml +++ b/.github/actions/setup-deps/action.yml @@ -1,5 +1,5 @@ name: Setup Dependencies -description: Clone ecosystem deps with retry, setup Go, create workspace +description: Clone ecosystem sibling repos with retry, setup Go, sync workspace inputs: go-version: @@ -12,7 +12,7 @@ inputs: runs: using: composite steps: - - name: Checkout dependencies + - name: Checkout sibling dependencies shell: bash env: GH_TOKEN: ${{ inputs.token }} @@ -25,21 +25,25 @@ runs: done echo "Failed to clone $repo after 3 attempts" && return 1 } - mkdir -p external - clone_with_retry hawk-core-contracts external/hawk-core-contracts main - clone_with_retry eyrie external/eyrie main - clone_with_retry tok external/tok main - clone_with_retry yaad external/yaad main - clone_with_retry inspect external/inspect main - clone_with_retry sight external/sight main - clone_with_retry trace external/trace main + ws_parent="$(cd "${GITHUB_WORKSPACE}/.." && pwd)" + mkdir -p "$ws_parent" + clone_with_retry hawk-core-contracts "$ws_parent/hawk-core-contracts" main + clone_with_retry eyrie "$ws_parent/eyrie" main + clone_with_retry tok "$ws_parent/tok" main + clone_with_retry yaad "$ws_parent/yaad" main + clone_with_retry inspect "$ws_parent/inspect" main + clone_with_retry sight "$ws_parent/sight" main + clone_with_retry trace "$ws_parent/trace" main + clone_with_retry hawk-mcpkit "$ws_parent/hawk-mcpkit" main - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ inputs.go-version }} - - name: Create workspace + - name: Sync workspace shell: bash run: | - printf 'go 1.26.6\n\nuse .\n\nreplace (\n\tgithub.com/GrayCodeAI/hawk-core-contracts => ./external/hawk-core-contracts\n\tgithub.com/GrayCodeAI/eyrie => ./external/eyrie\n\tgithub.com/GrayCodeAI/inspect => ./external/inspect\n\tgithub.com/GrayCodeAI/sight => ./external/sight\n\tgithub.com/GrayCodeAI/tok => ./external/tok\n\tgithub.com/GrayCodeAI/trace => ./external/trace\n\tgithub.com/GrayCodeAI/yaad => ./external/yaad\n\tgithub.com/GrayCodeAI/hawk-mcpkit => ./external/hawk-mcpkit\n)\n' > go.work + # hawk's committed go.work references the sibling repos; ensure it is + # consistent with the cloned siblings. + go work sync diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6176f914..bb1f65a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,9 +84,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: | - git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -104,11 +101,11 @@ jobs: fi - name: go mod verify run: go mod verify - - name: workspace points at external checkouts + - name: workspace references sibling checkouts run: | for module in hawk-core-contracts eyrie inspect sight tok trace yaad hawk-mcpkit; do - if ! grep -q "./external/${module}" go.work; then - echo "::error::go.work must include ./external/${module}." + if ! grep -q "../${module}" go.work; then + echo "::error::go.work must include ../${module}." cat go.work exit 1 fi @@ -133,9 +130,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: | - git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -149,13 +143,16 @@ jobs: go build -mod=readonly ./cmd/hawk go test ./... -count=1 -timeout=300s -skip='TestDefaultSkillDirsCrossAgent|TestCopySelectionE2E' - submodule-release-parity: - name: submodule and module parity + release-parity: + name: workspace and module parity runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + - uses: ./.github/actions/checkout-eyrie + with: + ref: ${{ github.head_ref || github.ref_name }} - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -175,9 +172,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: | - git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/compatibility-matrix.yml b/.github/workflows/compatibility-matrix.yml index b5bc0045..5c8185c8 100644 --- a/.github/workflows/compatibility-matrix.yml +++ b/.github/workflows/compatibility-matrix.yml @@ -26,10 +26,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - allow_branch_fallback: "true" - - name: Init hawk-mcpkit submodule - run: | - git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: "1.26.6" diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index 6ac85820..af4bffa4 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -11,7 +11,6 @@ on: - "packaging/systemd/hawk-daemon.service" - "internal/**" - "cmd/**" - - "external/**" - "go.mod" - "go.sum" @@ -31,8 +30,6 @@ jobs: steps: - name: Check out source uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b9478173..a945c411 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -37,10 +37,6 @@ jobs: image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:amd64-${{ github.sha }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - # Pull the pinned ecosystem submodules under external/ so the Docker - # build compiles against the integrated revisions, not each repo's main. - submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 @@ -115,10 +111,6 @@ jobs: image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:arm64-${{ github.sha }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - # Pull the pinned ecosystem submodules under external/ so the Docker - # build compiles against the integrated revisions, not each repo's main. - submodules: recursive - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7077661..c0c4a76d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,33 +22,10 @@ jobs: with: fetch-depth: 0 # goreleaser needs full history for changelog - # Releases must use Gitlink pins only — never a branch head fallback. + # Releases build against the sibling repos resolved via the workspace go.work. - uses: ./.github/actions/checkout-eyrie with: - allow_branch_fallback: "false" - - - name: Verify Gitlink pins present - run: | - set -euo pipefail - missing=0 - for repo in hawk-core-contracts eyrie inspect sight tok trace yaad; do - commit=$(git ls-tree HEAD "external/${repo}" | awk '{print $3}' || true) - if [ -z "$commit" ]; then - echo "::error::Release requires Gitlink for external/${repo}" - missing=1 - else - head=$(git -C "external/${repo}" rev-parse HEAD) - if [ "$head" != "$commit" ]; then - echo "::error::external/${repo} checked out $head, Gitlink wants $commit" - missing=1 - else - echo "OK external/${repo} @ ${commit:0:12}" - fi - fi - done - if [ "$missing" -ne 0 ]; then - exit 1 - fi + ref: ${{ github.ref_name }} - name: Set up Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 92f18c3d..00000000 --- a/.gitmodules +++ /dev/null @@ -1,24 +0,0 @@ -[submodule "external/eyrie"] - path = external/eyrie - url = https://github.com/GrayCodeAI/eyrie.git -[submodule "external/yaad"] - path = external/yaad - url = https://github.com/GrayCodeAI/yaad.git -[submodule "external/tok"] - path = external/tok - url = https://github.com/GrayCodeAI/tok.git -[submodule "external/sight"] - path = external/sight - url = https://github.com/GrayCodeAI/sight.git -[submodule "external/inspect"] - path = external/inspect - url = https://github.com/GrayCodeAI/inspect.git -[submodule "external/trace"] - path = external/trace - url = https://github.com/GrayCodeAI/trace.git -[submodule "external/hawk-core-contracts"] - path = external/hawk-core-contracts - url = https://github.com/GrayCodeAI/hawk-core-contracts.git -[submodule "hawk-mcpkit"] - path = external/hawk-mcpkit - url = https://github.com/GrayCodeAI/hawk-mcpkit.git diff --git a/AGENTS.md b/AGENTS.md index 1f88069d..1306f91a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,7 +192,7 @@ Legacy `hawk/shared/types` has been removed. Cross-repo severity and finding con ### Architecture note: provider ownership Implement provider protocols, adapters, catalog metadata, credential mappings, and -provider contract tests in `external/eyrie` first. Hawk consumes providers only +provider contract tests in `../eyrie` (the eyrie sibling repo) first. Hawk consumes providers only through Eyrie's stable engine facade; Hawk changes should be limited to host UX and facade integration. Concentrate AI is a pay-as-you-go gateway implemented with its native Responses API (`/v1/responses`) under the @@ -243,13 +243,12 @@ This project is indexed by GitNexus as **hawk** (97743 symbols, 322940 relations -### Submodule workflow (external/ repos) +### Workspace workflow (sibling repos) -hawk depends on ecosystem repos (`eyrie`, `hawk-core-contracts`, etc.) via git submodules under `external/`. Hawk's `go.work` points to `./external/`, so changes in the submodule are automatically picked up by hawk. +hawk depends on ecosystem repos (`eyrie`, `hawk-core-contracts`, etc.) as independent sibling repos in the `graycode-eco` workspace. Hawk's `go.work` lists them as `../`, so local changes in any sibling are automatically picked up by hawk. Each sibling is its own git repo, versioned and released independently. -1. Edit + test in `hawk/external/` — run its tests, run `make test` in hawk -2. Push from the submodule: `git push origin ` -3. Sync to the independent repo: `cd ../ && git fetch origin && git checkout ` -4. PR → merge from the independent repo -5. Pull main in the submodule: `cd ../hawk/external/ && git checkout main && git pull origin main` -6. Commit the pointer in hawk: `cd .. && git add external/ && git commit -m "chore: update "` +1. Edit + test in `../` — run its tests, run `make test` in hawk +2. Push from the sibling: `git push origin ` +3. Open a PR in the sibling repo → merge to `main` +4. Ensure hawk's `go.mod` pins a version that resolves to (or is an ancestor of) the sibling's `main` — run `make sync` to verify parity +5. No pointer commits: hawk resolves the sibling via `go.work` for local dev and via the pinned `go.mod` version for standalone/module-mode builds (Docker, released consumers) diff --git a/Dockerfile b/Dockerfile index 23a63ddd..a60275cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,10 @@ RUN apk upgrade --no-cache && \ WORKDIR /build -# GrayCodeAI sibling modules are unpublished at their current code (the public proxy -# froze v0.1.0 at old commits). Resolve them locally via a generated go.work -# (use . + replace => ./external/), bypassing the proxy/sumdb entirely. +# GrayCodeAI engine modules are published and pinned in go.mod at tagged or +# commit-pseudo versions, resolved from the module proxy. The committed +# go.work (which references sibling checkouts ../) is excluded from the +# build context, so build in module mode (no go.work) against those pins. ENV GOPRIVATE=github.com/GrayCodeAI/* \ GONOSUMDB=github.com/GrayCodeAI/* \ GONOSUMCHECK=1 @@ -25,12 +26,9 @@ ARG BUILD_DATE=unknown COPY . . -# external/ are committed submodules pinned to the integrated revisions -# (populated by `submodules: recursive` in .github/workflows/docker.yml, or -# `git submodule update --init --recursive` for a local `docker build`). Build -# against those pinned checkouts via a generated go.work — the committed -# go.work/go.work.sum are excluded by .dockerignore, and the public proxy froze -# v0.1.0 at older commits. Do NOT run 'go mod download' first. +# Build against the engine versions pinned in go.mod via the module proxy. The +# committed go.work/go.work.sum (sibling-checkout based) are excluded by +# .dockerignore and must not be present for a module-mode build. # # main.Version / main.Commit / main.BuildDate are baked in from the ARGs above; # this is the only correct source — `git describe` would always return empty @@ -43,10 +41,6 @@ COPY . . RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ rm -f go.work go.work.sum && \ - { echo "go 1.26.6"; echo; echo "use ."; echo; echo "replace ("; \ - for repo in hawk-core-contracts eyrie inspect sight tok trace yaad; do \ - echo " github.com/GrayCodeAI/${repo} => ./external/${repo}"; \ - done; echo ")"; } > go.work && \ CGO_ENABLED=0 GOOS=linux go build -trimpath \ -ldflags="-s -w \ -X main.Version=${VERSION} \ diff --git a/Dockerfile.daemon b/Dockerfile.daemon index b920ad11..5ed3b77e 100644 --- a/Dockerfile.daemon +++ b/Dockerfile.daemon @@ -24,10 +24,6 @@ COPY . . RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ rm -f go.work go.work.sum && \ - { echo "go 1.26.6"; echo; echo "use ."; echo; echo "replace ("; \ - for repo in hawk-core-contracts eyrie inspect sight tok trace yaad; do \ - echo " github.com/GrayCodeAI/${repo} => ./external/${repo}"; \ - done; echo ")"; } > go.work && \ CGO_ENABLED=0 GOOS=linux go build -trimpath \ -ldflags="-s -w \ -X main.Version=${VERSION} \ diff --git a/Makefile b/Makefile index 7484abb1..834076a7 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,8 @@ GORELEASER := $(GOBIN_DIR)/goreleaser # --------------------------------------------------------------------------- # Phony declarations (alphabetical). # --------------------------------------------------------------------------- -.PHONY: all bench boundaries build check-replace ci clean contracts-guard ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard internal-layers-guard package-boundaries-guard submodule-release-parity cover cover-new fmt help install lint lint-fix \ - release security setup smoke path sync-external test test-10x test-live test-new test-race tidy version vet api-docs api-validate +.PHONY: all bench boundaries build check-replace ci clean contracts-guard ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard internal-layers-guard package-boundaries-guard release-parity cover cover-new fmt help install lint lint-fix \ + release security setup smoke path sync test test-10x test-live test-new test-race tidy version vet api-docs api-validate check-replace: ## Fail if go.mod has local replace directives (run before tagging) @bash scripts/check-no-replace-directives.sh @@ -132,7 +132,7 @@ package-boundaries-guard: ## Enforce AST/package-graph boundaries with file/line boundaries: contracts-guard ecosystem-guard eyrie-client-guard eyrie-engine-guard peer-guard internal-layers-guard package-boundaries-guard ## Alias for all boundary guards (matches `make boundaries` in engine repos). -submodule-release-parity: ## Verify every go.mod ecosystem version resolves to its pinned Gitlink. +release-parity: ## Verify every go.mod ecosystem version is reachable from its sibling repo HEAD. bash ./scripts/check-submodule-release-parity.sh lint: ## Run golangci-lint. @@ -182,40 +182,18 @@ FOUNDATION_REPOS := hawk-core-contracts ECO_REPOS := eyrie inspect sight tok trace yaad hawk-mcpkit WORKSPACE_REPOS := $(FOUNDATION_REPOS) $(ECO_REPOS) -setup: ## Set up local development environment (go.work + external repos). +setup: ## Set up local development environment (workspace go.work + sibling repos). @echo "=== Setting up hawk development environment ===" - @mkdir -p external - @for repo in $(WORKSPACE_REPOS); do \ - dest="external/$$repo"; \ - if git -C "$$dest" rev-parse --git-dir >/dev/null 2>&1; then \ - echo "✓ $$dest already exists"; \ - else \ - commit=$$(git ls-tree HEAD "$$dest" 2>/dev/null | awk '{print $$3}' || true); \ - if [ -n "$$commit" ]; then \ - echo "Cloning $$repo at pinned commit $$commit..."; \ - git clone "https://github.com/GrayCodeAI/$$repo.git" "$$dest" 2>/dev/null && \ - (cd "$$dest" && git checkout --quiet "$$commit") || \ - echo " ⚠ Could not clone $$repo at pinned commit $$commit"; \ - else \ - ref=$$(git branch --show-current 2>/dev/null || echo main); \ - if ! git ls-remote --heads "https://github.com/GrayCodeAI/$$repo.git" "$$ref" | grep -q .; then \ - ref=main; \ - fi; \ - echo "Cloning $$repo at branch $$ref..."; \ - git clone --depth=1 --branch "$$ref" "https://github.com/GrayCodeAI/$$repo.git" "$$dest" 2>/dev/null || \ - echo " ⚠ Could not clone $$repo (may not exist yet or no access)"; \ - fi; \ - fi; \ - done - @echo "Generating go.work..." + @echo "Generating go.work (workspace model)..." @echo "go 1.26.6" > go.work @echo "" >> go.work - @echo "use ." >> go.work - @echo "" >> go.work - @echo "replace (" >> go.work + @echo "use (" >> go.work + @echo " ." >> go.work @for repo in $(WORKSPACE_REPOS); do \ - if [ -d "external/$$repo" ]; then \ - echo " github.com/GrayCodeAI/$$repo => ./external/$$repo" >> go.work; \ + if [ -d "../$$repo" ]; then \ + echo " ../$$repo" >> go.work; \ + else \ + echo " ⚠ sibling repo ../$$repo not found — clone it into the graycode-eco workspace"; \ fi; \ done @echo ")" >> go.work @@ -260,26 +238,14 @@ compat-check: ## Strict validation — non-zero exit if any component lacks a ve compat-drift: ## Advisory: report pin drift between hawk's go.mod and external/ submodules. Never fails. @go run ./cmd/compat-test -check-external -file=testdata/compatibility-matrix.json -.PHONY: hooks sync-submodules sync-submodule-versions sync-clone +.PHONY: hooks sync hooks: ## Install git hooks via lefthook (formatting, linting, conventional commits). @command -v lefthook >/dev/null 2>&1 || (echo "install: go install github.com/evilmartians/lefthook@latest" && exit 1) lefthook install -sync-submodules: ## Fetch and checkout latest origin/main for all external/ submodules. - git submodule foreach 'git fetch origin && git checkout origin/main 2>/dev/null || git checkout origin/HEAD' - @echo "Submodule heads:" - @git submodule status - -sync-submodule-versions: ## After advancing submodules, bump go.mod requires to match each gitlink. - @chmod +x scripts/sync-submodule-versions.sh - @./scripts/sync-submodule-versions.sh - -sync-external: ## Read-only drift report: external/ pin vs sibling ../ HEAD. - @bash ./scripts/sync-external.sh - -sync-clone: ## Hard-reset hawk and submodules to origin/main (post history rewrite). - @chmod +x scripts/sync-clone.sh scripts/commit-clean.sh - @./scripts/sync-clone.sh +sync: ## Sync the workspace go.work and verify sibling release parity. + @go work sync + @bash ./scripts/check-submodule-release-parity.sh # === Cross-platform binary targets (add after existing 'build' target) === .PHONY: build-all build-static size-check diff --git a/README.md b/README.md index 99b91080..97664f43 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ hawk path # verify readiness ```bash git clone https://github.com/GrayCodeAI/hawk && cd hawk -make setup # clones required support repos into external/ and syncs go.work +make setup # generates go.work referencing sibling support repos in the graycode-eco workspace go build -o hawk ./cmd/hawk ./hawk @@ -434,15 +434,14 @@ hawk/ ├── docs/ # Architecture, security, integration docs └── testdata/ # Test fixtures -External ecosystem modules (git submodules): -├── external/ -│ ├── eyrie/ # LLM provider runtime -│ ├── hawk-core-contracts/ # Shared cross-repo types -│ ├── inspect/ # Security audit library -│ ├── sight/ # Diff-based code review -│ ├── tok/ # Tokenizer, compression, secrets scanning -│ ├── trace/ # Session capture and replay -│ └── yaad/ # Graph-based persistent memory +Ecosystem sibling repos (in the graycode-eco workspace): +├── eyrie/ # LLM provider runtime +├── hawk-core-contracts/ # Shared cross-repo types +├── inspect/ # Security audit library +├── sight/ # Diff-based code review +├── tok/ # Tokenizer, compression, secrets scanning +├── trace/ # Session capture and replay +└── yaad/ # Graph-based persistent memory ``` ### Ecosystem @@ -456,8 +455,8 @@ hawk integrates these GrayCodeAI repos in three layers: Local development uses: - **`go.mod` modules:** pinned requirements for the support engines and `hawk-core-contracts` -- **External checkout + `go.work`:** clone support repos under `external/`; `go.work` maps the module paths to those local checkouts -- **Submodules in this repo:** the same external layout is pinned under `external/` for reproducible CI and multi-repo work +- **Workspace + `go.work`:** sibling support repos are cloned in the `graycode-eco` workspace (as `../`); `go.work` resolves the module paths to those local checkouts +- **Module-mode builds:** standalone / Docker builds resolve the pinned `go.mod` versions from the module proxy (no workspace) Cross-repo contracts now live in **`github.com/GrayCodeAI/hawk-core-contracts`** so support repos do not depend on Hawk internals. The old `hawk/shared/types` path has been removed; use `hawk-core-contracts/types` for shared severity and finding contracts. diff --git a/cmd/compat-test/drift.go b/cmd/compat-test/drift.go index c480ded9..f66e3cff 100644 --- a/cmd/compat-test/drift.go +++ b/cmd/compat-test/drift.go @@ -19,19 +19,19 @@ var trackedPins = []string{ } // checkDrift compares hawk's own go.mod requirements for trackedPins against -// what each external/ submodule (a locally pinned clone of a hawk dependency) -// declares for the same modules in its own go.mod. It never fails — this is -// advisory, printed for humans/CI logs to notice, not a build gate. +// what each sibling repo (a peer checkout of a hawk dependency in the shared +// workspace) declares for the same modules in its own go.mod. It never fails — +// this is advisory, printed for humans/CI logs to notice, not a build gate. func checkDrift(repoRoot string) error { hawkRequires, err := readRequires(filepath.Join(repoRoot, "go.mod")) if err != nil { return fmt.Errorf("read hawk go.mod: %w", err) } - externalDir := filepath.Join(repoRoot, "external") - entries, err := os.ReadDir(externalDir) + workspaceDir := filepath.Join(repoRoot, "..") + entries, err := os.ReadDir(workspaceDir) if err != nil { - return fmt.Errorf("read external/: %w", err) + return fmt.Errorf("read workspace (%s): %w", workspaceDir, err) } fmt.Println("Pin freshness (advisory — see docs/compatibility.md):") @@ -40,10 +40,10 @@ func checkDrift(repoRoot string) error { if !e.IsDir() { continue } - modPath := filepath.Join(externalDir, e.Name(), "go.mod") + modPath := filepath.Join(workspaceDir, e.Name(), "go.mod") consumerRequires, err := readRequires(modPath) if err != nil { - continue // submodule not checked out / no go.mod — skip silently + continue // sibling not a Go module / no go.mod — skip silently } for _, pin := range trackedPins { hawkVer, hawkHas := hawkRequires[pin] @@ -57,7 +57,7 @@ func checkDrift(repoRoot string) error { } } if drifted == 0 { - fmt.Println(" OK — no drift between hawk's pins and external/ consumers") + fmt.Println(" OK — no drift between hawk's pins and sibling consumers") } return nil } diff --git a/cmd/compat-test/drift_test.go b/cmd/compat-test/drift_test.go index d71e02a0..1d7114fc 100644 --- a/cmd/compat-test/drift_test.go +++ b/cmd/compat-test/drift_test.go @@ -70,15 +70,15 @@ func TestReadRequires_InvalidMod(t *testing.T) { // TestCheckDrift reports drift when a consumer pins an older version than hawk. func TestCheckDrift_DetectsDrift(t *testing.T) { - root := t.TempDir() - writeMod(t, filepath.Join(root, "go.mod"), `module github.com/GrayCodeAI/hawk + ws := t.TempDir() + writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk go 1.26 require github.com/GrayCodeAI/hawk-core-contracts v1.5.0 `) - // Consumer pins an older version of the shared contract. - writeMod(t, filepath.Join(root, "external", "inspect", "go.mod"), `module github.com/GrayCodeAI/inspect + // Consumer sibling pins an older version of the shared contract. + writeMod(t, filepath.Join(ws, "inspect", "go.mod"), `module github.com/GrayCodeAI/inspect go 1.26 @@ -89,7 +89,7 @@ require github.com/GrayCodeAI/hawk-core-contracts v1.2.0 old := os.Stdout r, w, _ := os.Pipe() os.Stdout = w - err := checkDrift(root) + err := checkDrift(filepath.Join(ws, "hawk")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -105,14 +105,14 @@ require github.com/GrayCodeAI/hawk-core-contracts v1.2.0 // TestCheckDrift_NoDriftWhenVersionsMatch verifies the happy path: matching // pins produce the "OK" line and no per-consumer drift lines. func TestCheckDrift_NoDriftWhenVersionsMatch(t *testing.T) { - root := t.TempDir() - writeMod(t, filepath.Join(root, "go.mod"), `module github.com/GrayCodeAI/hawk + ws := t.TempDir() + writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk go 1.26 require github.com/GrayCodeAI/hawk-core-contracts v1.5.0 `) - writeMod(t, filepath.Join(root, "external", "sight", "go.mod"), `module github.com/GrayCodeAI/sight + writeMod(t, filepath.Join(ws, "sight", "go.mod"), `module github.com/GrayCodeAI/sight go 1.26 @@ -123,7 +123,7 @@ require github.com/GrayCodeAI/hawk-core-contracts v1.5.0 r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(root) + err := checkDrift(filepath.Join(ws, "hawk")) _ = w.Close() os.Stdout = old _, _ = buf.ReadFrom(r) @@ -136,30 +136,30 @@ require github.com/GrayCodeAI/hawk-core-contracts v1.5.0 } } -// TestCheckDrift_SkipsMissingSubmodules verifies that a consumer directory -// without a go.mod (e.g. not checked out) is skipped without failing. -func TestCheckDrift_SkipsMissingSubmodules(t *testing.T) { - root := t.TempDir() - writeMod(t, filepath.Join(root, "go.mod"), `module github.com/GrayCodeAI/hawk +// TestCheckDrift_SkipsMissingSiblings verifies that a sibling directory +// without a go.mod (e.g. not a Go module) is skipped without failing. +func TestCheckDrift_SkipsMissingSiblings(t *testing.T) { + ws := t.TempDir() + writeMod(t, filepath.Join(ws, "hawk", "go.mod"), `module github.com/GrayCodeAI/hawk go 1.26 require github.com/GrayCodeAI/hawk-core-contracts v1.5.0 `) // Directory present but no go.mod — must be skipped silently. - if err := os.MkdirAll(filepath.Join(root, "external", "not-checked-out"), 0o755); err != nil { + if err := os.MkdirAll(filepath.Join(ws, "not-checked-out"), 0o755); err != nil { t.Fatal(err) } r, w, _ := os.Pipe() old := os.Stdout os.Stdout = w - err := checkDrift(root) + err := checkDrift(filepath.Join(ws, "hawk")) _ = w.Close() os.Stdout = old _, _ = io.Copy(io.Discard, r) if err != nil { - t.Fatalf("checkDrift with missing submodule go.mod: %v", err) + t.Fatalf("checkDrift with missing sibling go.mod: %v", err) } } diff --git a/cmd/compat-test/main.go b/cmd/compat-test/main.go index f352e26a..8f7ae7d8 100644 --- a/cmd/compat-test/main.go +++ b/cmd/compat-test/main.go @@ -20,7 +20,7 @@ // go run ./cmd/compat-test -check-external # advisory: compare hawk's own // # go.mod pins for shared leaf // # deps against what each -// # external/ submodule declares. +// # sibling repo declares. // # Always exits 0; see drift.go. package main @@ -52,7 +52,7 @@ func main() { matrixName := flag.String("matrix", "next", "matrix entry to inspect (next, stable, ...)") strict := flag.Bool("strict", false, "exit non-zero if any component lacks a pinned version") path := flag.String("file", findMatrixFile(), "path to compatibility-matrix.json") - checkExternal := flag.Bool("check-external", false, "advisory: report pin drift against external/ submodules and exit (see drift.go)") + checkExternal := flag.Bool("check-external", false, "advisory: report pin drift against sibling repos and exit (see drift.go)") flag.Parse() if *path == "" { diff --git a/docs/DYNAMIC-MODELS.md b/docs/DYNAMIC-MODELS.md index ffa7b223..574c8c51 100644 --- a/docs/DYNAMIC-MODELS.md +++ b/docs/DYNAMIC-MODELS.md @@ -100,9 +100,9 @@ hosts must be isolated from one another. 2. Add Eyrie tests for cache and live discovery, credential status, selection, and generation/streaming. 3. Commit and verify standalone Eyrie. -4. Advance Hawk's `external/eyrie` submodule to that exact commit, then update +4. Advance Hawk's `../eyrie` sibling checkout to that exact commit, then update Hawk's module version when the Eyrie revision is published. -5. Verify both the clean submodule (`go.work`) and published-module +5. Verify both the workspace (`go.work`) and published-module (`GOWORK=off`) build modes. Hawk changes are needed only for a new product behavior or an additive diff --git a/docs/architecture.md b/docs/architecture.md index a08631b8..fa17a859 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -52,7 +52,7 @@ hawk/ │ ├── bridge/ link Bridges to ecosystem services │ └── resilience/ refresh-cw Circuit breaker, retry, rate limit ├── docs/ book-open Architecture docs -└── external/ link Pinned ecosystem submodules for go.work integration +└── (ecosystem siblings live at ../ in the graycode-eco workspace; see docs/architecture/ecosystem-design.md) ``` Legacy note: `hawk/shared/types` has been removed. Shared cross-repo severity @@ -129,5 +129,5 @@ Tool Call → `: + +```go +go 1.26.6 +use ( + . + ../eyrie + ../yaad + ../tok + ../trace + ../sight + ../inspect + ../hawk-core-contracts + ../hawk-mcpkit +) +``` + +- No `git submodule`s, no committed `replace` directives. +- Clone all repos into `graycode-eco/`, run `make setup` in `hawk` to regenerate + `go.work`, then `make sync` / `go work sync`. +- Local edits in any sibling are picked up immediately by hawk's build. + +### 3. Published connectivity: Go module versions + +Standalone and module-mode builds (Docker, released consumers) resolve the engine +versions pinned in `hawk/go.mod` from the module proxy. Each engine is released +independently with its own semver tag. `hawk-core-contracts` is released first, +then engines bump to it, then hawk bumps to the engines. + +Release order: **contracts → engines → hawk.** + +### 4. HTTP connectivity (non-Go components) + +Components outside the Go module graph connect over public APIs: + +- **`hawk-cloud`** (Cloudflare Worker) — hosted control plane (tenancy, usage, + audit, optional sync). Hawk syncs to it via fail-open, user-approved HTTP + (`POST /v1/graph/sync`, usage events). Needed only when a user opts into cloud. +- **`graycode-core`** — the GrayCode web/backend monorepo. Not a Hawk runtime + dependency. It consumes `hawk-cloud`'s public API; hawk sends only opt-in, + fail-open telemetry to it over HTTP (ADR-0001). Never a Go import. +- **`hawk-graph`** (Node dashboard) — dev tooling that reads architecture data; + no Go module link. + +## Enforcement guards (scripts + CI) + +| Guard | Script / target | +|---|---| +| No engine→engine imports | `check-support-repo-coupling.sh` | +| No engine→hawk imports | `check-ecosystem-boundaries.sh` | +| No legacy `shared/types` | `check-shared-types-imports.sh` | +| Eyrie only via `engine` facade | `check-eyrie-client-imports.sh`, `check-eyrie-engine-boundary.sh` | +| No committed `replace` | `check-no-replace-directives.sh` | +| go.mod version reachable from sibling HEAD | `check-submodule-release-parity.sh` (`make release-parity`) | + +## Best practices + +- Bump `hawk-core-contracts` first; keep all engines on the same latest tag at + each ecosystem release to avoid version skew. +- Never commit `replace` directives into `go.mod`. +- Add a CI integration matrix so each engine builds against the latest contracts + and one job compiles hawk + all engines together. diff --git a/docs/architecture/plan.md b/docs/architecture/plan.md index 6c283f41..7cb3799c 100644 --- a/docs/architecture/plan.md +++ b/docs/architecture/plan.md @@ -151,10 +151,10 @@ services; it is not a claim that those fields have already moved. ## Dependencies -- **eyrie:** LLM provider engine behind `eyrie/engine` (external submodule) -- **yaad:** Graph-based persistent memory (external submodule) -- **tok:** Tokenizer, compression (external submodule) -- **hawk-core-contracts:** Shared types (external submodule) +- **eyrie:** LLM provider engine behind `eyrie/engine` (workspace sibling) +- **yaad:** Graph-based persistent memory (workspace sibling) +- **tok:** Tokenizer, compression (workspace sibling) +- **hawk-core-contracts:** Shared types (workspace sibling) ## Success Metrics diff --git a/docs/architecture/spec.md b/docs/architecture/spec.md index f2473399..c1419ca5 100644 --- a/docs/architecture/spec.md +++ b/docs/architecture/spec.md @@ -28,7 +28,7 @@ layout: |-----------|---------| | `cmd/` | CLI entry point (Cobra) and TUI (Bubble Tea) | | `internal/` | Private Go packages (not importable by external repos) | -| `external/` | Pinned ecosystem submodules (eyrie, yaad, tok, inspect, sight, trace, hawk-core-contracts) | +| `go.work` | Resolves ecosystem siblings (eyrie, yaad, tok, inspect, sight, trace, hawk-core-contracts) | | `spec/` | OpenSpec schema consumed by `internal/spec` | | `docs/` | Architecture docs, design docs, plans | | `rules/` | User-defined rules | diff --git a/docs/architecture/tasks.md b/docs/architecture/tasks.md index 62989705..8ac600ff 100644 --- a/docs/architecture/tasks.md +++ b/docs/architecture/tasks.md @@ -18,7 +18,7 @@ ### Repository Structure (REQ-1, REQ-2) - [ ] Verify all internal/ packages exist and are documented -- [ ] Verify external/ submodules are pinned correctly +- [ ] Verify go.work sibling repos are checked out correctly - [ ] Verify spec/ reference repos are up to date - [ ] Check for orphaned or unused packages diff --git a/docs/troubleshooting-guide.md b/docs/troubleshooting-guide.md index ccddc737..e1b80d44 100644 --- a/docs/troubleshooting-guide.md +++ b/docs/troubleshooting-guide.md @@ -85,9 +85,11 @@ curl -v http://localhost:4590/v1/ready Check the response body for the specific failed check. Common causes: - No model configured — set `HAWK_MODEL` or provider credentials. -- Eyrie catalog not initialized — ensure submodules are checked out: +- Eyrie catalog not initialized — ensure the ecosystem siblings are present in + the graycode-eco workspace and run `make setup` in hawk (regenerates `go.work`): ```bash - git submodule update --init --recursive + make setup + go work sync ``` ### `GET /v1/health` returns 503 diff --git a/external/eyrie b/external/eyrie deleted file mode 160000 index 6057740d..00000000 --- a/external/eyrie +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6057740d18c72f38aaec90515427af4efdc7deae diff --git a/external/hawk-core-contracts b/external/hawk-core-contracts deleted file mode 160000 index 16ebcfd5..00000000 --- a/external/hawk-core-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16ebcfd5ad6e298c9cace718af8e958d2e3e299f diff --git a/external/hawk-mcpkit b/external/hawk-mcpkit deleted file mode 160000 index 82c1c610..00000000 --- a/external/hawk-mcpkit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 82c1c610efe3bf4390da964035708b364cbcb216 diff --git a/external/inspect b/external/inspect deleted file mode 160000 index 8556ee05..00000000 --- a/external/inspect +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8556ee05ff07459cd546057b40aae2d97843b829 diff --git a/external/sight b/external/sight deleted file mode 160000 index 39553454..00000000 --- a/external/sight +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 39553454cd601d5f13d3c983520a9a10f1bb016f diff --git a/external/tok b/external/tok deleted file mode 160000 index a7c4b99d..00000000 --- a/external/tok +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a7c4b99d37b8241d43e838f9d3c648a70fdc22f1 diff --git a/external/trace b/external/trace deleted file mode 160000 index 59b437bb..00000000 --- a/external/trace +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 59b437bbe8dc62f1d601cd9f124ad9aad58b4b97 diff --git a/external/yaad b/external/yaad deleted file mode 160000 index c968f179..00000000 --- a/external/yaad +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c968f1798a5b7c839ba13db1f313c38032fd9543 diff --git a/go.work b/go.work index e741f6c7..fa4d62fe 100644 --- a/go.work +++ b/go.work @@ -1,14 +1,13 @@ go 1.26.6 -use . - -replace ( - github.com/GrayCodeAI/eyrie => ./external/eyrie - github.com/GrayCodeAI/hawk-core-contracts => ./external/hawk-core-contracts - github.com/GrayCodeAI/hawk-mcpkit => ./external/hawk-mcpkit - github.com/GrayCodeAI/inspect => ./external/inspect - github.com/GrayCodeAI/sight => ./external/sight - github.com/GrayCodeAI/tok => ./external/tok - github.com/GrayCodeAI/trace => ./external/trace - github.com/GrayCodeAI/yaad => ./external/yaad +use ( + . + ../eyrie + ../hawk-core-contracts + ../hawk-mcpkit + ../inspect + ../sight + ../tok + ../trace + ../yaad ) diff --git a/scripts/check-ecosystem-boundaries.sh b/scripts/check-ecosystem-boundaries.sh index e97e3ae2..235189c6 100755 --- a/scripts/check-ecosystem-boundaries.sh +++ b/scripts/check-ecosystem-boundaries.sh @@ -7,13 +7,6 @@ cd "$ROOT_DIR" pattern='github\.com/GrayCodeAI/hawk/(internal/|shared/types)' violations="" -external_hits="$( - git grep -n -E "${pattern}" -- 'external/**/*.go' || true -)" -if [[ -n "${external_hits}" ]]; then - violations+="${external_hits}"$'\n' -fi - for repo in ../sight ../inspect ../tok ../trace ../yaad ../eyrie; do if [[ -d "${repo}" ]]; then repo_hits="$( diff --git a/scripts/check-submodule-release-parity.sh b/scripts/check-submodule-release-parity.sh index 8cdedc13..f1566f7d 100755 --- a/scripts/check-submodule-release-parity.sh +++ b/scripts/check-submodule-release-parity.sh @@ -1,39 +1,48 @@ #!/usr/bin/env bash set -euo pipefail -# Compare each engine Gitlink (from the superproject index) to the commit -# resolved by the go.mod module version. Uses `git ls-tree` so the check -# works without `git submodule update` / checkout-eyrie having populated -# external/* working trees. +# Workspace release parity: for each Go module hawk requires, verify that the +# version pinned in hawk's go.mod resolves to a commit reachable from the +# sibling repo's HEAD. This catches hawk pinning a version that the sibling +# repo has not published / is behind on. Run from the hawk repo root with the +# ecosystem cloned as siblings (../) in the graycode-eco workspace. repos=(hawk-core-contracts eyrie inspect sight tok trace yaad hawk-mcpkit) failed=0 -printf '%-24s %-14s %-14s %s\n' MODULE GITLINK MODULE_COMMIT STATUS +printf '%-24s %-14s %-14s %s\n' MODULE MODULE_COMMIT SIBLING_HEAD STATUS for repo in "${repos[@]}"; do module="github.com/GrayCodeAI/${repo}" - gitlink=$(git ls-tree HEAD "external/${repo}" | awk '{print $3}') - if [[ -z "$gitlink" ]]; then - printf '%-24s %-14s %-14s %s\n' "$repo" missing - MISSING_GITLINK - failed=1 + sibling="../${repo}" + + version=$(GOWORK=off go list -m -f '{{.Version}}' "$module" 2>/dev/null || true) + if [[ -z "$version" ]]; then + printf '%-24s %-14s %-14s %s\n' "$repo" unknown - NOT_REQUIRED continue fi - version=$(GOWORK=off go list -m -f '{{.Version}}' "$module") metadata=$(GOWORK=off go mod download -json "${module}@${version}" 2>/dev/null || true) module_commit=$(printf '%s\n' "$metadata" | sed -n 's/.*"Hash": "\([0-9a-f]*\)".*/\1/p' | head -1) + + if [[ ! -d "$sibling/.git" ]]; then + printf '%-24s %-14s %-14s %s\n' "$repo" "${module_commit:0:12}" - NO_SIBLING + failed=1 + continue + fi + sibling_head=$(git -C "$sibling" rev-parse HEAD 2>/dev/null || echo "") + if [[ -z "$module_commit" ]]; then - printf '%-24s %-14s %-14s %s\n' "$repo" "${gitlink:0:12}" unknown UNRESOLVED + printf '%-24s %-14s %-14s %s\n' "$repo" unknown "${sibling_head:0:12}" UNRESOLVED failed=1 - elif [[ "$module_commit" == "$gitlink" ]]; then - printf '%-24s %-14s %-14s %s\n' "$repo" "${gitlink:0:12}" "${module_commit:0:12}" OK + elif git -C "$sibling" merge-base --is-ancestor "$module_commit" "$sibling_head" 2>/dev/null; then + printf '%-24s %-14s %-14s %s\n' "$repo" "${module_commit:0:12}" "${sibling_head:0:12}" OK else - printf '%-24s %-14s %-14s %s\n' "$repo" "${gitlink:0:12}" "${module_commit:0:12}" MISMATCH + printf '%-24s %-14s %-14s %s\n' "$repo" "${module_commit:0:12}" "${sibling_head:0:12}" AHEAD_OF_SIBLING failed=1 fi done if ((failed)); then - echo "submodule/module release parity failed" >&2 + echo "workspace/module release parity failed" >&2 exit 1 fi diff --git a/scripts/check-support-repo-coupling.sh b/scripts/check-support-repo-coupling.sh index b37c16d0..3d504c58 100644 --- a/scripts/check-support-repo-coupling.sh +++ b/scripts/check-support-repo-coupling.sh @@ -34,7 +34,6 @@ scan_dir() { } for repo in "${support_repos[@]}"; do - scan_dir "${repo}" "external/${repo}" scan_dir "${repo}" "../${repo}" done diff --git a/scripts/sync-clone.sh b/scripts/sync-clone.sh deleted file mode 100755 index fb32fb4b..00000000 --- a/scripts/sync-clone.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -# Hard-reset hawk and all external/ submodules to origin/main. -# Use after a history rewrite or when your clone has stale SHAs. -set -euo pipefail - -cd "$(git rev-parse --show-toplevel)" - -# Guard: this script hard-resets hawk AND all submodules, destroying any -# uncommitted work. Refuse to run on a dirty tree unless explicitly forced. -if [ -n "$(git status --porcelain)" ] && [ "${SYNC_CLONE_FORCE:-}" != "1" ]; then - echo "sync-clone: working tree has uncommitted changes." >&2 - echo "This will HARD-RESET hawk and all external/ submodules to origin/main." >&2 - echo "Commit/stash your work, or re-run with SYNC_CLONE_FORCE=1 to proceed." >&2 - exit 1 -fi - -echo "==> Fetching origin" -git fetch origin - -echo "==> Resetting hawk to origin/main" -git checkout main -git reset --hard origin/main - -echo "==> Updating submodules" -git submodule update --init --recursive -git submodule foreach 'git fetch origin && git checkout origin/main 2>/dev/null || git checkout origin/HEAD' - -echo "==> Done. hawk: $(git rev-parse --short HEAD)" -git submodule status diff --git a/scripts/sync-external.sh b/scripts/sync-external.sh deleted file mode 100755 index 876274de..00000000 --- a/scripts/sync-external.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash -# Read-only drift report: compares each external/ submodule's pinned -# commit against the HEAD of the sibling dev clone at ../ (relative to -# the graycode-eco workspace root). Unlike `make sync-submodules` (which mutates -# the submodule checkout), this makes no changes — it only reports. -# -# Typical drift: you commit changes in ../tok, but forget `make -# sync-submodules` + a commit in hawk to bump the external/tok pin. This -# script catches that before it becomes a stale-dependency surprise in CI. -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT_DIR" - -if [[ ! -f .gitmodules ]]; then - echo "no .gitmodules file found — nothing to check" - exit 0 -fi - -exit_code=0 -printf '%-28s %-10s %-10s %s\n' "SUBMODULE" "PINNED" "SIBLING" "STATUS" - -while IFS= read -r path; do - name="$(basename "$path")" - sibling="../$name" - - pinned="$(git ls-tree HEAD "$path" 2>/dev/null | awk '{print $3}')" - if [[ -z "$pinned" ]]; then - printf '%-28s %-10s %-10s %s\n' "$path" "none" "-" "NOT-PINNED (submodule never committed)" - exit_code=1 - continue - fi - pinned_short="${pinned:0:10}" - - if [[ ! -d "$sibling/.git" ]]; then - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "-" "NO-SIBLING (expected clone at $sibling)" - exit_code=1 - continue - fi - - sibling_head="$(git -C "$sibling" rev-parse HEAD 2>/dev/null || echo "")" - if [[ -z "$sibling_head" ]]; then - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "-" "SIBLING-UNREADABLE" - exit_code=1 - continue - fi - sibling_short="${sibling_head:0:10}" - - if [[ "$pinned" == "$sibling_head" ]]; then - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "$sibling_short" "OK" - elif git -C "$sibling" merge-base --is-ancestor "$pinned" "$sibling_head" 2>/dev/null; then - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "$sibling_short" "BEHIND (sibling has newer commits)" - exit_code=1 - elif git -C "$sibling" merge-base --is-ancestor "$sibling_head" "$pinned" 2>/dev/null; then - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "$sibling_short" "AHEAD (pin is newer than local sibling checkout)" - exit_code=1 - else - printf '%-28s %-10s %-10s %s\n' "$path" "$pinned_short" "$sibling_short" "DIVERGED (different history)" - exit_code=1 - fi -done < <(git config -f .gitmodules --get-regexp path | awk '{print $2}') - -if [[ $exit_code -ne 0 ]]; then - echo - echo "drift detected — run 'make sync-submodules' then 'make sync-submodule-versions'" - echo "(advances the submodule working trees, then bumps hawk's go.mod requires to match the gitlinks), then commit the updated external/ pins" -fi - -exit $exit_code diff --git a/scripts/sync-submodule-versions.sh b/scripts/sync-submodule-versions.sh deleted file mode 100755 index 0d071e53..00000000 --- a/scripts/sync-submodule-versions.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -# Bump every external/ submodule's requirement in hawk's go.mod to the exact -# commit the submodule pointer (gitlink) is pinned to. -# -# Hawk tracks its engine dependencies two ways: -# 1. as git submodules external/ -# 2. as Go module requires github.com/GrayCodeAI/ -# in go.mod. These must agree, otherwise `make submodule-release-parity` fails. -# -# `make sync-submodules` advances the submodule working trees, but does NOT -# update go.mod. This script is the companion "sync-versions" step: for every -# submodule that maps to a go.mod require, it runs `go get @` -# so the pseudo-version in go.mod resolves to the same commit as the gitlink. -# -# Run from the repo root: -# make sync-submodules # advance + checkout external/ trees -# make sync-submodule-versions # THEN bump go.mod to match -# -# The script is read-only until it writes — it prints each update and exits -# non-zero on the first `go get` that can't resolve a published commit. -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT_DIR" - -if [[ ! -f .gitmodules ]]; then - echo "sync-submodule-versions: no .gitmodules — nothing to do" - exit 0 -fi - -# Submodule paths that are also Go modules hawk requires. -# (Derived from WORKSPACE_REPOS in Makefile plus hawk-mcpkit.) Parallel arrays -# keep this portable across bash 3.2 (macOS /bin/bash) and bash 5.x (CI). -repos=( - eyrie - inspect - sight - tok - trace - yaad - hawk-core-contracts - hawk-mcpkit -) -mods=( - github.com/GrayCodeAI/eyrie - github.com/GrayCodeAI/inspect - github.com/GrayCodeAI/sight - github.com/GrayCodeAI/tok - github.com/GrayCodeAI/trace - github.com/GrayCodeAI/yaad - github.com/GrayCodeAI/hawk-core-contracts - github.com/GrayCodeAI/hawk-mcpkit -) - -echo "Syncing go.mod require versions to external/ submodule gitlinks:" -exit_code=0 -for i in "${!repos[@]}"; do - repo="external/${repos[$i]}" - mod="${mods[$i]}" - gitlink="$(git ls-tree HEAD "$repo" 2>/dev/null | awk '{print $3}')" - if [[ -z "$gitlink" ]]; then - echo " $repo: MISSING_GITLINK (submodule never committed) — skipping" - continue - fi - echo " $repo ($mod): $gitlink -> go get $mod@$gitlink" - if ! go get "$mod@$gitlink"; then - echo " ERROR: could not resolve $mod@$gitlink (commit must be pushed)" >&2 - exit_code=1 - fi -done - -if [[ $exit_code -ne 0 ]]; then - echo "sync-submodule-versions: one or more go get calls failed" >&2 - exit $exit_code -fi - -echo "Tidying go.mod / go.sum..." -go mod tidy - -echo "Verifying submodule/module release parity..." -bash ./scripts/check-submodule-release-parity.sh - -echo -echo "Sync complete. go.mod now tracks each external/ submodule's gitlink." -echo "Review with: git diff go.mod go.sum" -echo "Then commit: git add go.mod go.sum external/ && git commit -m 'chore: sync external/ module versions'" diff --git a/scripts/verify-milestone.sh b/scripts/verify-milestone.sh index 08b6d399..def651bd 100755 --- a/scripts/verify-milestone.sh +++ b/scripts/verify-milestone.sh @@ -4,17 +4,17 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" -echo "== eyrie (external) ==" -EYRIE="./external/eyrie" +echo "== eyrie (sibling) ==" +EYRIE="../eyrie" if [[ -d "$EYRIE" ]]; then (cd "$EYRIE" && go test ./... -count=1 -short) else - echo "skip: ./external/eyrie not found" + echo "skip: ../eyrie not found" fi -echo "== external ecosystem modules ==" +echo "== sibling ecosystem modules ==" for module in yaad tok sight inspect trace; do - dir="./external/$module" + dir="../$module" if [[ -d "$dir" ]]; then (cd "$dir" && go test ./... -count=1 -short) else From 8c4ebe58afa93ca0e203061bb186d5a74592ed5a Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 12:28:00 +0530 Subject: [PATCH 2/7] chore: replace pre-push external-drift hook with workspace release-parity --- lefthook.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lefthook.yml b/lefthook.yml index 5ca6637c..ee17b025 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -124,11 +124,11 @@ pre-push: boundary-ecosystem: run: bash scripts/check-ecosystem-boundaries.sh - external-drift: + release-parity: run: | - # Informational only — warns if external/ submodule pins have fallen - # behind the sibling dev clones, but never blocks a push. - bash scripts/sync-external.sh || true + # Informational only — warns if go.mod versions have fallen behind the + # sibling dev clones, but never blocks a push. + bash scripts/check-submodule-release-parity.sh || true # --------------------------------------------------------------------------- # commit-msg — validate Conventional Commits and strip AI co-author trailers. From cb110385320da5fd57f4969d88d75ba8dde000a1 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 12:49:52 +0530 Subject: [PATCH 3/7] fix(ci): remove remaining hawk-mcpkit submodule init steps Several jobs (deadcode, test, lint, security, fuzz, smoke) still ran 'git submodule update --init external/hawk-mcpkit', which fails now that the submodules are removed. Remove all remaining occurrences; the checkout-eyrie action clones the siblings into the workspace parent. --- .github/workflows/ci.yml | 19 ++----------------- .github/workflows/compatibility-matrix.yml | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb1f65a6..fc9313f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ concurrency: env: GO_VERSION: "1.26.6" - # GrayCodeAI sibling modules are resolved from the local external/ submodules via - # go.work; their go.mod require versions (v0.1.0) intentionally do not match the + # GrayCodeAI sibling modules are resolved from the workspace ../ checkouts via + # go.work; their go.mod require versions intentionally do not match the # frozen public proxy/sumdb snapshot, so bypass the proxy + checksum DB for them. GOPRIVATE: "github.com/GrayCodeAI/*" GONOSUMDB: "github.com/GrayCodeAI/*" @@ -197,8 +197,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -228,8 +226,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -300,8 +296,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -410,9 +404,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: | - git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: go-version: "${{ env.GO_VERSION }}" @@ -471,8 +462,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -518,8 +507,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} @@ -547,8 +534,6 @@ jobs: - uses: ./.github/actions/checkout-eyrie with: ref: ${{ github.head_ref || github.ref_name }} - - name: Init hawk-mcpkit submodule - run: git submodule update --init external/hawk-mcpkit - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} diff --git a/.github/workflows/compatibility-matrix.yml b/.github/workflows/compatibility-matrix.yml index 5c8185c8..e52499ae 100644 --- a/.github/workflows/compatibility-matrix.yml +++ b/.github/workflows/compatibility-matrix.yml @@ -34,6 +34,6 @@ jobs: run: make compat-check - name: Report 'next' matrix run: make compat-test - - name: Pin freshness vs external/ (advisory) + - name: Pin freshness vs sibling repos (advisory) run: make compat-drift continue-on-error: true From 7c6142e4c64fd3b3f365f5234343ee052de6e7d1 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 13:12:48 +0530 Subject: [PATCH 4/7] ci: re-baseline Trivy gate for CVE-2026-14456 (OpenSSL not yet in Alpine 3.23) OpenSSL libcrypto3 3.5.7-r0 has CVE-2026-14456 (HIGH, DoS). The fix (3.5.8-r0) is not yet published in the Alpine 3.23 repository (latest alpine:3.23 still ships 3.5.7-r0). Hawk is a Go binary that does not link libcrypto, so this is not reachable at runtime. Add the CVE to .trivyignore with justification until Alpine publishes the patch, and wire the ignorefile into the Docker Trivy scans. --- .github/workflows/daemon-image.yml | 1 + .github/workflows/docker.yml | 3 +++ .trivyignore | 10 ++++++++++ 3 files changed, 14 insertions(+) create mode 100644 .trivyignore diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index af4bffa4..705fe240 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -70,6 +70,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true + ignorefile: .trivyignore exit-code: '1' - name: Generate image metadata diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a945c411..a7b534cd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -78,6 +78,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true + ignorefile: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -152,6 +153,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true + ignorefile: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -265,6 +267,7 @@ jobs: # reviewed separately from the runtime OS scan. vuln-type: os ignore-unfixed: true + ignorefile: .trivyignore exit-code: '1' - name: Build and publish public sandbox image diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 00000000..8dca84a9 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,10 @@ +# Trivy OS-package ignore list for Hawk Docker images. +# +# CVE-2026-14456 — OpenSSL DoS via unbounded memory (libcrypto3/libssl3). +# Fixed upstream in OpenSSL 3.5.8-r0, but that package is NOT yet published in +# the Alpine 3.23 repository (the latest alpine:3.23 still ships 3.5.7-r0, as +# of 2026-08-27). Hawk is a Go binary and does not link libcrypto; this affects +# only the base OS TLS stack and is not reachable from Hawk's runtime. Re-add a +# base-image bump to remove this entry once Alpine 3.23 publishes OpenSSL +# 3.5.8-r0. +CVE-2026-14456 From 5b09ca60a69463b79b2a9a1a8d4c09a09dadad5f Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 13:23:37 +0530 Subject: [PATCH 5/7] fix(ci): use trivyignores input for the Trivy ignore file --- .github/workflows/daemon-image.yml | 2 +- .github/workflows/docker.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index 705fe240..a3fe5df8 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -70,7 +70,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - ignorefile: .trivyignore + trivyignores: .trivyignore exit-code: '1' - name: Generate image metadata diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a7b534cd..96e51103 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -78,7 +78,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - ignorefile: .trivyignore + trivyignores: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -153,7 +153,7 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - ignorefile: .trivyignore + trivyignores: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -267,7 +267,7 @@ jobs: # reviewed separately from the runtime OS scan. vuln-type: os ignore-unfixed: true - ignorefile: .trivyignore + trivyignores: .trivyignore exit-code: '1' - name: Build and publish public sandbox image From 222e62f48abb48e7a31dd56f77eb2ae5344c5e05 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 13:39:32 +0530 Subject: [PATCH 6/7] fix(ci): let trivy auto-load .trivyignore instead of trivyignores input The trivy-action trivyignores input sets TRIVY_IGNOREFILE to a relative ./trivyignores path that does not resolve in CI. Trivy auto-loads .trivyignore from its working directory by default, so drop the input and rely on the committed .trivyignore at the repo root. --- .github/workflows/daemon-image.yml | 1 - .github/workflows/docker.yml | 3 --- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index a3fe5df8..af4bffa4 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -70,7 +70,6 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - trivyignores: .trivyignore exit-code: '1' - name: Generate image metadata diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 96e51103..a945c411 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -78,7 +78,6 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - trivyignores: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -153,7 +152,6 @@ jobs: # non-reachable packages that Trivy reports as binary findings. vuln-type: os ignore-unfixed: true - trivyignores: .trivyignore exit-code: '1' # Block publishing images with actionable vulnerabilities # Second build is a cache hit (layers exported by the scan build), so it @@ -267,7 +265,6 @@ jobs: # reviewed separately from the runtime OS scan. vuln-type: os ignore-unfixed: true - trivyignores: .trivyignore exit-code: '1' - name: Build and publish public sandbox image From 6b1a49a87a2340cc9965731935e52c9c2542aa8c Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Thu, 27 Aug 2026 13:51:51 +0530 Subject: [PATCH 7/7] ci: pass --ignorefile explicitly to trivy for the OS scan trivy in CI does not auto-load .trivyignore from its working directory, and the trivy-action's trivyignores input resolves the ignore file relative to a path trivy cannot see. Replace the trivy-action scan steps with setup-trivy + an explicit 'trivy image --ignorefile ' command so CVE-2026-14456 (OpenSSL; fixed libcrypto 3.5.8-r0 not yet in Alpine 3.23) is honored. --- .github/workflows/daemon-image.yml | 25 ++++++---- .github/workflows/docker.yml | 73 ++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/.github/workflows/daemon-image.yml b/.github/workflows/daemon-image.yml index af4bffa4..71fdf0c6 100644 --- a/.github/workflows/daemon-image.yml +++ b/.github/workflows/daemon-image.yml @@ -59,18 +59,27 @@ jobs: BUILD_DATE=${{ github.event.head_commit.timestamp }} - name: Scan daemon image with Trivy - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - format: sarif - output: trivy-daemon-image.sarif - severity: CRITICAL,HIGH + version: v0.70.0 + cache: true + - name: Run Trivy daemon scan (sarif) + shell: bash + run: | # Go reachability is enforced separately by govulncheck in CI. The # binary also carries the full workspace module graph, including # non-reachable packages that Trivy reports as binary findings. - vuln-type: os - ignore-unfixed: true - exit-code: '1' + # CVE-2026-14456 (OpenSSL) is ignored via .trivyignore — the fixed + # libcrypto 3.5.8-r0 is not yet published in Alpine 3.23. + trivy image \ + --severity CRITICAL,HIGH \ + --ignore-unfixed \ + --ignorefile "${GITHUB_WORKSPACE}/.trivyignore" \ + --vuln-type os \ + --format sarif \ + --output trivy-daemon-image.sarif \ + --exit-code 1 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - name: Generate image metadata id: meta diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a945c411..b6630429 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -67,18 +67,27 @@ jobs: BUILD_DATE=${{ github.event.head_commit.timestamp }} - name: Scan image with Trivy - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - format: sarif - output: trivy-image.sarif - severity: CRITICAL,HIGH + version: v0.70.0 + cache: true + - name: Run Trivy scan (sarif) + shell: bash + run: | # Go reachability is enforced separately by govulncheck in CI. The # binary also carries the full workspace module graph, including # non-reachable packages that Trivy reports as binary findings. - vuln-type: os - ignore-unfixed: true - exit-code: '1' # Block publishing images with actionable vulnerabilities + # CVE-2026-14456 (OpenSSL) is ignored via .trivyignore — the fixed + # libcrypto 3.5.8-r0 is not yet published in Alpine 3.23. + trivy image \ + --severity CRITICAL,HIGH \ + --ignore-unfixed \ + --ignorefile "${GITHUB_WORKSPACE}/.trivyignore" \ + --vuln-type os \ + --format sarif \ + --output trivy-image.sarif \ + --exit-code 1 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan # Second build is a cache hit (layers exported by the scan build), so it # only re-links and pushes the platform image. @@ -141,18 +150,27 @@ jobs: BUILD_DATE=${{ github.event.head_commit.timestamp }} - name: Scan image with Trivy - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 with: - image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan - format: sarif - output: trivy-image.sarif - severity: CRITICAL,HIGH + version: v0.70.0 + cache: true + - name: Run Trivy scan (sarif) + shell: bash + run: | # Go reachability is enforced separately by govulncheck in CI. The # binary also carries the full workspace module graph, including # non-reachable packages that Trivy reports as binary findings. - vuln-type: os - ignore-unfixed: true - exit-code: '1' # Block publishing images with actionable vulnerabilities + # CVE-2026-14456 (OpenSSL) is ignored via .trivyignore — the fixed + # libcrypto 3.5.8-r0 is not yet published in Alpine 3.23. + trivy image \ + --severity CRITICAL,HIGH \ + --ignore-unfixed \ + --ignorefile "${GITHUB_WORKSPACE}/.trivyignore" \ + --vuln-type os \ + --format sarif \ + --output trivy-image.sarif \ + --exit-code 1 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:scan # Second build is a cache hit (layers exported by the scan build), so it # only re-links and pushes the platform image. @@ -254,18 +272,25 @@ jobs: cache-to: type=gha,mode=max,scope=hawk-sandbox - name: Scan sandbox image - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 with: - image-ref: ${{ env.REGISTRY }}/graycodeai/hawk-sandbox:scan - format: sarif - output: trivy-sandbox-image.sarif - severity: CRITICAL,HIGH + version: v0.70.0 + cache: true + - name: Run Trivy sandbox scan (sarif) + shell: bash + run: | # This image gate covers the Debian OS package surface. npm's # bundled CLI dependency tree is pinned by the Node base image and # reviewed separately from the runtime OS scan. - vuln-type: os - ignore-unfixed: true - exit-code: '1' + trivy image \ + --severity CRITICAL,HIGH \ + --ignore-unfixed \ + --ignorefile "${GITHUB_WORKSPACE}/.trivyignore" \ + --vuln-type os \ + --format sarif \ + --output trivy-sandbox-image.sarif \ + --exit-code 1 \ + ${{ env.REGISTRY }}/graycodeai/hawk-sandbox:scan - name: Build and publish public sandbox image uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0