diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7391815 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,41 @@ +# Anvil ships Linux binaries and Linux-targeted deploy assets, and its CI runs +# on ubuntu-latest. Line endings are therefore LF everywhere, enforced here +# rather than left to each contributor's core.autocrlf setting. +# +# Without this, a Windows checkout gets CRLF working-tree files, `gofmt -l` +# reports every Go file as unformatted locally while CI (which sees LF) passes, +# and local verification stops being trustworthy. Every packet in +# plan/IMPLEMENTATION-PLAN.md owes build/test evidence, so local verification +# has to mean something. + +* text=auto eol=lf + +*.go text eol=lf diff=golang +*.sql text eol=lf +*.py text eol=lf +*.sh text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.json text eol=lf +*.md text eol=lf +*.toml text eol=lf +go.mod text eol=lf +go.sum text eol=lf +Makefile text eol=lf +Dockerfile text eol=lf + +# systemd units are parsed on Linux only and must never carry CR. +*.service text eol=lf +*.timer text eol=lf +*.slice text eol=lf + +# Model weights, archives, and fixture corpora. +*.gguf binary +*.safetensors binary +*.onnx binary +*.db binary +*.sqlite binary +*.tar binary +*.gz binary +*.zip binary +*.png binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c85b20b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,180 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + go: + name: Go build, vet, test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + # Single source of truth: the toolchain line in go.mod. + go-version-file: go.mod + cache: true + + - name: go mod tidy is a no-op + run: | + cp go.mod go.mod.bak + cp go.sum go.sum.bak + go mod tidy + diff -u go.mod.bak go.mod + diff -u go.sum.bak go.sum + rm go.mod.bak go.sum.bak + + - name: gofmt + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "::error::gofmt needed on:" + echo "$unformatted" + gofmt -d . + exit 1 + fi + + - name: go vet + run: go vet ./... + + - name: go build + run: go build ./... + + - name: go test + run: go test -race -count=1 ./... + + # plan/00-SPINE.md S9-AMENDED splits Anvil into two distribution artifacts: + # anvil (core, no network-probing capability compiled in) and anvil-dast. + # plan/IMPLEMENTATION-PLAN.md 2.2 rules that a config flag inside a single + # binary does not address the concern the split exists for. This job is what + # makes the split real rather than conventional. + artifact-split: + name: Two-artifact split (S9-AMENDED) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Core binary has no DAST capability in its import graph + run: go test ./cmd/anvil -run TestSplit -v + + - name: Negative control -- the guard must be able to fail + run: | + set -e + mkdir -p internal/dast/ciprobe + cat > internal/dast/ciprobe/probe.go <<'EOF' + package ciprobe + + const Sentinel = "ci-negative-control" + EOF + cat > cmd/anvil/ci_negctl.go <<'EOF' + package main + + import _ "github.com/Susquehanna-Syntax/Anvil/internal/dast/ciprobe" + EOF + if go test ./cmd/anvil -run TestSplit; then + echo "::error::TestSplit PASSED with an internal/dast import present. The guard is a no-op." + exit 1 + fi + echo "guard correctly failed on the injected violation" + rm -f cmd/anvil/ci_negctl.go + rm -rf internal/dast/ciprobe + go test ./cmd/anvil -run TestSplit + + - name: Static, cgo-free builds for both artifacts + env: + CGO_ENABLED: "0" + run: | + set -e + for goarch in amd64 arm64; do + for target in anvil anvil-dast; do + GOOS=linux GOARCH=$goarch go build -o "/tmp/${target}-linux-${goarch}" "./cmd/${target}" + file "/tmp/${target}-linux-${goarch}" + done + done + # S12 mandates modernc.org/sqlite precisely so no cgo/C toolchain is + # needed. mattn/go-sqlite3 would reintroduce both. + if go list -deps ./... | grep -q 'github.com/mattn/go-sqlite3'; then + echo "::error::mattn/go-sqlite3 is in the dependency graph. S12 mandates modernc.org/sqlite." + exit 1 + fi + + plan: + name: Plan cross-reference invariants + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + + # plan/IMPLEMENTATION-PLAN.md 2.6: "Re-run it after any edit to an area + # file." Making that a CI job is the only way it actually happens. + - name: tools/plan_xref.py + run: python tools/plan_xref.py + + - name: tools/plan_fields.py parses + run: python tools/plan_fields.py > /dev/null + + licence: + name: Licence hygiene + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # plan/00-SPINE.md S8: read LICENSE file bodies, never API metadata. + # Seven audited artifacts return NOASSERTION over a real licence and one + # hides a restrictive licence behind a permissive tag. + - name: LICENSE is canonical Apache-2.0 + run: | + expected=cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 + actual=$(sha256sum LICENSE | cut -d' ' -f1) + if [ "$actual" != "$expected" ]; then + echo "::error::LICENSE sha256 is $actual, expected the canonical Apache-2.0 $expected" + exit 1 + fi + + - name: Required compliance files exist + run: | + set -e + for f in LICENSE NOTICE THIRD-PARTY-LICENSES.md; do + test -s "$f" || { echo "::error::$f missing or empty (plan step C.1)"; exit 1; } + done + + # plan/00-SPINE.md S5 is enforced, not advisory. This is a coarse + # placeholder for cmd/license-gate (plan steps C.9/C.10), which reads + # licence bodies properly; it exists so the exclusion list is not purely + # documentary between now and Phase 6. + - name: Hard exclusions (S5) are absent from the dependency graph + run: | + set -e + fail=0 + # Deliberately narrow: module paths only, so prose in plan/ and + # NOTICE -- which must be free to *name* these to explain the + # exclusions -- does not trip the gate. + for banned in \ + "github.com/github/codeql" \ + "github.com/returntocorp/semgrep" \ + "github.com/opengrep/opengrep-rules" \ + "github.com/AFLplusplus/AFLplusplus" \ + "github.com/mattn/go-sqlite3" ; do + if grep -rn --include=go.mod --include=go.sum "$banned" . ; then + echo "::error::$banned is on the S5 hard exclusion list" + fail=1 + fi + done + exit $fail diff --git a/go.mod b/go.mod index a6ca375..daba57d 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/Susquehanna-Syntax/Anvil go 1.26.5 +require modernc.org/sqlite v1.56.0 + require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect @@ -12,5 +14,4 @@ require ( modernc.org/libc v1.74.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.56.0 // indirect ) diff --git a/go.sum b/go.sum index 9869c9c..1932692 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,50 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= +modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k= modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0= modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/buildpin/pin.go b/internal/buildpin/pin.go new file mode 100644 index 0000000..b54d3bc --- /dev/null +++ b/internal/buildpin/pin.go @@ -0,0 +1,24 @@ +// Package buildpin holds Phase 0 dependency pins alive in the module graph +// until the packages that actually use them exist. +// +// plan/00-SPINE.md S12 mandates modernc.org/sqlite, and +// plan/spine-c-language.md C5 pins it at v1.56.0. plan/IMPLEMENTATION-PLAN.md +// Phase 0 requires that dependency be added at bootstrap. But nothing imports +// it yet -- the store is plan step R.4 and its FTS5 startup guard is R.5 -- +// and `go mod tidy` removes a module that no package in the module imports. +// Without this file, CI's tidy check would silently delete the pin the +// bootstrap was supposed to establish. +// +// DELETE THIS PACKAGE when internal/store lands (R.4). It has no runtime +// purpose and must never be imported by anything other than itself. +package buildpin + +import ( + // modernc.org/sqlite: the cgo-free SQLite driver S12 mandates. Do NOT + // substitute mattn/go-sqlite3 -- it needs cgo and a C toolchain, which + // breaks the single static binary and the cross-compilation matrix. + // Verified at bootstrap against this exact version: SQLite 3.53.3, FTS5 + // virtual tables and MATCH queries both work. Licence read as a file + // body per S8: BSD-3-Clause. + _ "modernc.org/sqlite" +)