Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0051c85
test/ci: PG 14-18 dev database, TLS tests, docs-only gating, hooks
Kiran01bm Aug 4, 2026
e55146c
test/ci: PG 14-18 dev database, TLS tests, docs-only gating, hooks
Kiran01bm Aug 4, 2026
d89c872
docs: coverage invariant + TM-* test-methodology registry
Kiran01bm Aug 4, 2026
764f02e
Enforce mechanical style rules via lint; add judgment maxims to AGENT…
Kiran01bm Aug 4, 2026
5cfa021
docs: adopt the layered DDL-understanding decision (go-pgquery + exec…
Kiran01bm Aug 4, 2026
5cb78f1
Phase 1: optimistic front door with typed not-native-safe verdicts
Kiran01bm Aug 4, 2026
6fee893
Cover status active-session rendering and budgetVerdict lock branch
Kiran01bm Aug 4, 2026
4ea0e6c
Add logging principles, slog lint hygiene, and a --debug diagnostics …
Kiran01bm Aug 4, 2026
4adba6f
Make CLI tests assert typed outcomes, not wording; add verdict cause
Kiran01bm Aug 4, 2026
d626da7
Phase 2.1: desired-state parse boundary and execute-and-introspect
Kiran01bm Aug 4, 2026
4fcdefc
Phase 2.2: declarative diff plus diff and fmt commands
Kiran01bm Aug 4, 2026
6881497
ci: pin golangci-lint v2 and name test matrix jobs
Kiran01bm Aug 4, 2026
f61d263
ci: rename workflow to CI and the ci-ok sentinel to all-green
Kiran01bm Aug 5, 2026
14c9dee
ci: pin golangci-lint-action to commit SHA
Kiran01bm Aug 5, 2026
e06ed27
Phase 2.3a: typed operation descriptors and advisory rewrites
Kiran01bm Aug 5, 2026
b08f7b8
Phase 2.3b: classifier routing native / copy-and-swap / refuse
Kiran01bm Aug 5, 2026
16c42c0
Phase 2.4: router seam assigning classified statements to backends
Kiran01bm Aug 5, 2026
d4e45d4
ci: pin golangci-lint-action and lint binary version
Kiran01bm Aug 5, 2026
70d274e
ci: pin golangci-lint-action and lint binary version
Kiran01bm Aug 5, 2026
8ed1454
docs: port reviewed SchemaBot AGENTS.md conventions
Kiran01bm Aug 6, 2026
97f22f0
Merge remote-tracking branch 'origin/kiran01bm/ci-foundations' into k…
Kiran01bm Aug 6, 2026
f44207e
Add the two project lenses to AGENTS.md and review checks
Kiran01bm Aug 6, 2026
11e4a71
Harden the front door per PR #5 reviews
Kiran01bm Aug 6, 2026
9ce57f2
Merge remote-tracking branch 'origin/kiran01bm/phase-1-front-door' in…
Kiran01bm Aug 6, 2026
4e24b46
Address PR #6 review: FK refusal, serial adoption, change kinds, fmt …
Kiran01bm Aug 6, 2026
d91061b
Merge remote-tracking branch 'origin/kiran01bm/phase-2-1-2-2-diff' in…
Kiran01bm Aug 7, 2026
f700319
planner, router: fail closed on unconstructed safer rewrites
Kiran01bm Aug 7, 2026
76ee9dc
Merge pull request #7 from block/kiran01bm/phase-2-3-2-4-classifier-r…
Kiran01bm Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .agents/checks/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Review Checks

Checks for AI review agents. The authoritative rules live in [SAFETY.md](../../SAFETY.md),
[AGENTS.md](../../AGENTS.md), and [docs/tcb-model.md](../../docs/tcb-model.md) — this file is
the reviewer's distillation.

- Judge the change through both project lenses: (a) OSS-first — pg-sprite as the preferred
standalone PostgreSQL online-DDL tool (CLI usable without an orchestrator, external-user
docs and errors, no Block-internal assumptions); (b) clean SchemaBot integration — a stable
adapter-friendly seam (library API, verdict/plan JSON, error taxonomy) with the core never
depending on SchemaBot. Flag changes that serve one lens at the other's expense without a
recorded decision.
- Look up every touched `pkg/` package in the SAFETY.md partition table first — the review bar
differs between the safety-critical core and the periphery. Flag core changes with 🌶️ and
state the blast radius (data corruption, lost writes, wrong-table swap, stranded slot).
- Core packages: every loop, queue, retry, and wait must be bounded. An unbounded anything in
a core package is a review-blocking defect.
- Dangerous APIs accept proof types (`statement.Classified`, `PreflightedTable`,
`VerifiedShadow`, `CleanWatermark`, `TableLock`) with package-private constructors — never a
raw string or bool that a caller could fabricate. Core code re-verifies its own
preconditions; it never trusts that the planner or CLI checked.
- Invariant enforcement points carry a `// INV: <id>` comment matching
[docs/invariants.md](../../docs/invariants.md); violations use `ErrInvariantViolation`
naming the ID and abort fail-closed — never a warning, never retried.
- New dependencies inside a core package require a recorded decision (core dependency list:
`pgx/v5`, `pglogrepl`, stdlib). `github.com/block/spirit` must never be imported as a
module — ideas are ported with citations, not code.
- Connections go through `pkg/dbconn` (bounded `lock_timeout` / `statement_timeout`) — flag
raw `pgx` pools in production code.
- SQL parsing goes through `wasilibs/go-pgquery` (Wasm `libpg_query`); flag
`strings.Split(";")`, any hand-parsing, and imports of the cgo `pg_query_go` (documented
escape hatch, not the default). A parse failure is an error surfaced to the caller.
Shadow-table DDL and checkpoint fingerprints come from execute-and-introspect on the scratch
database — flag AST surgery that constructs the shadow schema or fingerprints SQL text.
- Generated SQL quotes every user-supplied or introspected identifier
(`pgx.Identifier{...}.Sanitize()` / `quote_ident()`) — flag raw interpolation of names into
SQL. Connection strings are parsed and re-serialized (`pgx.ParseConfig`), never
string-manipulated.
- Terminology: "schema change", not "migration", in code, CLI output, error messages, and new
docs — flag new occurrences except citations of external sources.
- Errors: wrapped with context and identifiers; no log-and-continue, no silent branch cases,
no discarded `Close()` errors, no `nolint`, no `--no-verify`. No panics in library code —
invariant violations return `ErrInvariantViolation` fail-closed. Postgres errors are matched
by SQLSTATE (`errors.As` → `*pgconn.PgError`, `.Code`), never by message text.
- Goroutines: every goroutine has an owner, a bounded lifetime, and a stop path — flag
fire-and-forget `go func()`. Core logic takes time from an injected clock — flag inline
`time.Now()`/`time.Sleep` in core packages.
- Comments describe *what* and *why*, never history — flag bug/PR references,
"previously X" notes, and counts or thresholds that will go stale. Log messages state what
*will* happen, not what *might*. No internal company details (cluster names, hostnames,
org names) in code, comments, commits, or PRs.
- Tests: real PostgreSQL for core logic (no mocked-DB tests), testify, `t.Context()`
(cleanups use `context.WithoutCancel(t.Context())`), named polling deadlines — flag bare
`time.Sleep` readiness waits and any timeout increase that masks a flake instead of fixing
the root cause.
- CI coverage: behavior that varies by PostgreSQL major must be exercised across the
supported matrix (14–18), not just the default version.
1 change: 1 addition & 0 deletions .cursorrules
48 changes: 48 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/sh
set -e

# Pre-commit: fast local feedback on staged Go files. Docs-only commits
# (no staged .go files) skip the Go checks entirely so Markdown edits stay
# instant. CI remains the authoritative gate.
#
# Enable with: make setup (sets core.hooksPath .githooks)

# Ensure we're at the worktree root (not the git common dir). Unset GIT_DIR
# first — git sets it during hooks and it makes subprocess git commands
# resolve the wrong repo root.
unset GIT_DIR
cd "$(git rev-parse --show-toplevel)"

staged_go=$(git diff --cached --name-only --diff-filter=d -- '*.go')
if [ -z "$staged_go" ]; then
echo "pre-commit: no staged Go files; skipping Go checks."
exit 0
fi

# Ensure GOROOT matches the Go version in go.mod. External tools (hermit,
# asdf) may set GOROOT to a different version, causing "compile: version
# does not match go tool version" errors in golangci-lint and go build.
GO_VERSION=$(sed -n 's/^go //p' go.mod | head -1)
if [ -n "$GO_VERSION" ]; then
for candidate in \
"/opt/homebrew/opt/go/libexec" \
"/usr/local/go" \
"$HOME/sdk/go${GO_VERSION}" \
"$HOME/Library/Caches/hermit/pkg/go-${GO_VERSION}"; do
if [ -x "$candidate/bin/go" ]; then
CANDIDATE_VERSION=$("$candidate/bin/go" version 2>/dev/null | grep -o "go${GO_VERSION}" || true)
if [ -n "$CANDIDATE_VERSION" ]; then
export GOROOT="$candidate"
export PATH="$GOROOT/bin:$PATH"
break
fi
fi
done
fi
export PATH="$PATH:$HOME/go/bin:/usr/local/go/bin:/opt/homebrew/bin"

# Everything staged must still build.
go build ./...

# gofmt + golangci-lint on staged files: auto-fix, re-stage, verify.
scripts/lint-fix.sh
115 changes: 115 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/bin/sh
set -e

# Pre-push: run unit tests (go test -race, integration skipped) for the
# packages touched by the commits being pushed. Docs-only pushes skip
# entirely. CI remains the authoritative gate and runs the full suite
# including the PostgreSQL matrix — this hook does not replace it.
#
# Reads the standard pre-push stdin records:
# <local ref> <local sha> <remote ref> <remote sha>

unset GIT_DIR
cd "$(git rev-parse --show-toplevel)"

remote_name="$1"

# Ensure GOROOT matches the Go version in go.mod (see pre-commit).
GO_VERSION=$(sed -n 's/^go //p' go.mod | head -1)
if [ -n "$GO_VERSION" ]; then
for candidate in \
"/opt/homebrew/opt/go/libexec" \
"/usr/local/go" \
"$HOME/sdk/go${GO_VERSION}" \
"$HOME/Library/Caches/hermit/pkg/go-${GO_VERSION}"; do
if [ -x "$candidate/bin/go" ]; then
CANDIDATE_VERSION=$("$candidate/bin/go" version 2>/dev/null | grep -o "go${GO_VERSION}" || true)
if [ -n "$CANDIDATE_VERSION" ]; then
export GOROOT="$candidate"
export PATH="$GOROOT/bin:$PATH"
break
fi
fi
done
fi
export PATH="$PATH:$HOME/go/bin:/usr/local/go/bin:/opt/homebrew/bin"

# Resolve a base ref for new-branch pushes (no remote sha yet): prefer the
# push remote's default branch, then origin/main, then main.
base_ref=""
for cand in "refs/remotes/$remote_name/HEAD" "refs/remotes/$remote_name/main" "refs/remotes/origin/main" "main"; do
if git rev-parse --verify --quiet "$cand" >/dev/null 2>&1; then
base_ref="$cand"
break
fi
done

# is_zero_sha returns success when the sha is all zeros (branch create/delete
# sentinel), independent of SHA-1 vs SHA-256 length.
is_zero_sha() {
case "$1" in
*[!0]*) return 1 ;;
*) return 0 ;;
esac
}

changed_go_files=""
while read -r local_ref local_sha remote_ref remote_sha; do
# Skip branch deletions — nothing to test.
if is_zero_sha "$local_sha"; then
continue
fi

if is_zero_sha "$remote_sha"; then
# New branch on the remote: test what this branch introduces relative
# to the base. Fall back to the single tip commit if no base exists.
if [ -n "$base_ref" ]; then
range_base=$(git merge-base "$base_ref" "$local_sha" 2>/dev/null || true)
else
range_base=""
fi
if [ -z "$range_base" ]; then
range_base="${local_sha}~1"
fi
else
range_base="$remote_sha"
fi

files=$(git diff --name-only --diff-filter=d "$range_base" "$local_sha" -- '*.go' 2>/dev/null || true)
if [ -n "$files" ]; then
if [ -z "$changed_go_files" ]; then
changed_go_files="$files"
else
changed_go_files="$changed_go_files
$files"
fi
fi
done

changed_go_files=$(printf '%s\n' "$changed_go_files" | sed '/^$/d' | sort -u)
if [ -z "$changed_go_files" ]; then
echo "pre-push: no Go changes in pushed commits; skipping unit tests."
exit 0
fi

# Map changed files to package directories that still exist and contain Go
# source for the default build.
pkgs=""
for dir in $(printf '%s\n' "$changed_go_files" | xargs -n1 dirname | sort -u); do
testable_pkg=$(go list -e -f '{{if or (or .GoFiles .TestGoFiles) .XTestGoFiles}}{{.ImportPath}}{{end}}' "./$dir" 2>/dev/null || true)
if [ -d "$dir" ] && [ -n "$testable_pkg" ]; then
pkgs="$pkgs ./$dir"
fi
done

pkgs=$(printf '%s' "$pkgs" | sed 's/^ *//')
if [ -z "$pkgs" ]; then
echo "pre-push: changed Go files map to no testable packages; skipping unit tests."
exit 0
fi

echo "pre-push: running unit tests (race, integration skipped) for changed packages:"
for p in $pkgs; do echo " $p"; done

# shellcheck disable=SC2086
SKIP_INTEGRATION=1 go test -race -count=1 $pkgs
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
92 changes: 88 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,99 @@
name: ci
name: CI

on:
push:
branches: [main]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
# Detect whether the change touches anything other than *.md / docs/**.
# PRs that touch only docs skip the heavy jobs below; pushes to main
# always run. Fails open: if detection fails, treat as a code change.
changes:
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
code: ${{ steps.out.outputs.code }}
steps:
- name: Detect non-docs changes
if: github.event_name == 'pull_request'
id: filter
continue-on-error: true
uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3.0.3
with:
filters: |
code:
- '**'
- '!**/*.md'
- '!docs/**'
- name: Resolve code-change flag
id: out
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
FILTER_OUTCOME: ${{ steps.filter.outcome }}
FILTER_CODE: ${{ steps.filter.outputs.code }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" != "pull_request" ]; then
code=true
elif [ "$FILTER_OUTCOME" != "success" ]; then
echo "::warning::paths-filter failed; running CI instead of skipping"
code=true
else
code="$FILTER_CODE"
fi
echo "code=$code" >> "$GITHUB_OUTPUT"
echo "event=$EVENT_NAME code=$code"

lint:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@v6
# Pin the same golangci-lint major used locally (v2 config format);
# the action's default binary lags and cannot load a v2 config.
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.12.2

build:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: make build
# The no-cgo promise is a contract: the Wasm parser keeps `go install`
# toolchain-free. One accidental import of the cgo escape hatch
# (pg_query_go's parser) would silently start requiring a C toolchain
# on every contributor's machine.
- name: Build with CGO disabled
run: go build ./...
env:
CGO_ENABLED: "0"

# The integration suite runs against every Aurora-supported PostgreSQL
# major (see the version-support research doc): the version floor is a
# promise CI enforces, not documentation.
# major (see docs/postgresql-version-support.md): the version floor is a
# promise CI enforces, not documentation. These are vanilla PostgreSQL
# images — real Aurora engine-version validation is a separate gate that
# cannot run in public CI.
test:
name: test (PostgreSQL ${{ matrix.pg }})
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand All @@ -41,3 +107,21 @@ jobs:
with:
go-version-file: go.mod
- run: make test

# Single required status for branch protection ("all-green" is the
# context to require). Succeeds when nothing failed — including
# docs-only PRs where the heavy jobs were skipped.
all-green:
if: always()
needs: [changes, lint, build, test]
runs-on: ubuntu-latest
steps:
- name: Check job results
env:
RESULTS: ${{ toJSON(needs) }}
run: |
echo "$RESULTS"
if echo "$RESULTS" | grep -Eq '"result": *"(failure|cancelled)"'; then
echo "a required job failed or was cancelled"
exit 1
fi
Loading
Loading