8000 feat!: add standalone Go contract tracer by dominicnunez · Pull Request #1 · dominicnunez/contract-tracer · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8e6c339
Publish standalone tracer with CI and contributor hooks
dominicnunez Oct 4, 2026
488e6e0
Raise minimum supported Go version to 1.27
dominicnunez Oct 4, 2026
1c5ed88
Align test modules and workspaces with Go 1.27
dominicnunez Oct 4, 2026
5b1417b
fix(module): declare the canonical public module path
dominicnunez Oct 4, 2026
f395016
fix(config): reject malformed configured names and symbols
dominicnunez Oct 4, 2026
f86a0d2
fix(hooks): validate every pushed ref before publication
dominicnunez Oct 4, 2026
6a9415e
fix(analysis): trace semantic operations in package initializers
dominicnunez Oct 4, 2026
62ced1c
fix(config): reject whitespace inside configured names
dominicnunez Oct 4, 2026
f9f8c3f
fix(storage): reject SQL symlinks and contain file reads
dominicnunez Oct 4, 2026
1a22ad9
fix(snapshot)!: verify architecture and experiment settings
dominicnunez Oct 4, 2026
e4c9fe8
fix(analysis): preserve uncertainty in compound assignments
dominicnunez Oct 4, 2026
146164b
fix(lifecycle): model discarded constructor results 8000
dominicnunez Oct 4, 2026
fdd17b1
fix(storage): stop SQL inventory at nested modules
dominicnunez Oct 4, 2026
4305c0c
fix(events): analyze typed positional struct literals
dominicnunez Oct 4, 2026
9992020
fix(analysis): retain multi-result assignment uncertainty
dominicnunez Oct 4, 2026
7a5abca
fix(analysis): canonicalize selected module roots
dominicnunez Oct 4, 2026
476013c
fix(snapshot): recheck inputs before publishing results
dominicnunez Oct 7, 2026
ce6d63f
fix(cli): prevent report output from replacing snapshots
dominicnunez Oct 7, 2026
90f6733
fix(paths)!: preserve filesystem traversal semantics
dominicnunez Oct 7, 2026
b8426e6
fix(cli)!: reject ambiguous Windows artifact names
dominicnunez Oct 7, 2026
421eae7
fix(paths): match Windows root lookup semantics
dominicnunez Oct 7, 2026
cc1c0ba
fix(windows): align workspace loader path aliases
dominicnunez Oct 7, 2026
e349657
fix(analysis): retain global address escape candidates
dominicnunez Oct 7, 2026
6062ea7
fix(storage): recognize modeled prepared statement executions
dominicnunez Oct 7, 2026
c695478
fix(lifecycle): merge generic AfterFunc source candidates
dominicnunez Oct 7, 2026
2142b9e
fix(analysis): retain uncalled private callback uncertainty
dominicnunez Oct 7, 2026
4891402
test: build temporary helper without VCS stamping
dominicnunez Oct 7, 2026
0480572
fix(events): retain uncertainty in range assignments
dominicnunez Oct 7, 2026
99704ac
fix(config): validate handler selectors before value filtering
dominicnunez Oct 7, 2026
5088ff3
fix(analysis): separate unresolved spans from literal strings
dominicnunez Oct 7, 2026
f7f73c5
fix(analysis): distinguish same-line initializer owners
dominicnunez Oct 7, 2026
c00f832
fix(storage): inventory SQL through bound database methods
dominicnunez Oct 7, 2026
8efedbc
fix(cli)!: protect inputs during saved analysis publication
dominicnunez Oct 7, 2026
bb534c0
fix(events): unwrap parenthesized field targets
dominicnunez Oct 7, 2026
b9a4ee7
fix(lifecycle): merge generic context parent candidates
dominicnunez Oct 7, 2026
5f0c0e4
fix(analysis): trace callbacks in bound external receivers
dominicnunez Oct 7, 2026
4ae2f75
fix(lifecycle): retain WaitGroup interface uncertainty
dominicnunez Oct 7, 2026
5827a05
fix(render): escape source text in Markdown reports
dominicnunez Oct 7, 2026
2f9ac68
fix(cli): protect configuration from artifact writes
dominicnunez Oct 7, 2026
a95d154
fix(analysis)!: verify selected dependency source sets
dominicnunez Oct 7, 2026
006ac47
fix(analysis): verify embedded asset match sets
dominicnunez Oct 7, 2026
e2910ff
fix(cli)!: protect inputs in report-only output
dominicnunez Oct 11, 2026
12305d4
fix(flow): preserve map reference alternatives
dominicnunez Oct 12, 2026
3f66e43
fix(flow): retain dependency interface alternatives
dominicnunez Oct 12, 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
71 changes: 71 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CI

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

permissions:
contents: read

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

jobs:
go:
name: Go ${{ matrix.go }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
go: ['1.27.x']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ matrix.go }}
cache: true
cache-dependency-path: go.sum
- run: go test ./... -count=1
- run: go vet ./...
- run: go build ./cmd/contract-trace

lint:
name: Pre-commit and configuration checks
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.27.x'
- run: python -m pip install -r requirements-dev.txt
- run: pre-commit run --all-files --hook-stage pre-commit

hook-integration:
name: Hook helpers on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- run: python -m unittest discover -s tools -p 'test_*.py'
74 changes: 74 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
fail_fast: true
minimum_pre_commit_version: '4.6.2'
default_install_hook_types: [pre-commit]
default_stages: [pre-commit]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c
hooks:
- id: check-yaml
stages: [pre-commit]
- id: check-json
stages: [pre-commit]
- id: trailing-whitespace
stages: [pre-commit]
- id: end-of-file-fixer
stages: [pre-commit]
- id: check-merge-conflict
stages: [pre-commit]
- id: detect-private-key
stages: [pre-commit]

- repo: https://github.com/rhysd/actionlint
rev: 914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12
hooks:
- id: actionlint
language_version: system
stages: [pre-commit]

- repo: local
hooks:
- id: gofmt-check
name: Check Go formatting
entry: python tools/check_gofmt.py
language: system
types: [go]
exclude: ^testdata/
pass_filenames: true
stages: [pre-commit]
- id: check-push-state
name: Verify pushed revision and clean worktree
entry: python tools/check_push_state.py
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
< 10BC8 /td> - id: validate-hook-config
name: Validate hook configuration
entry: pre-commit validate-config
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
- id: go-vet
name: Go vet
entry: go vet ./...
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
- id: go-test
name: Go tests
entry: go test ./... -count=1
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
- id: go-build
name: Build CLI
entry: go build ./cmd/contract-trace
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
99 changes: 97 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,100 @@
# Contract Tracer

A Go tool for discovering source-backed investigation scope across calls, values, storage, events, and lifecycle relationships.
Contract Tracer is a standalone Go library and command-line tool for discovering a bounded, evidence-backed investigation scope from source symbols or locations and an invariant hypothesis. It reports modeled calls, references, value flow, storage, event and lifecycle relationships together with candidate alternatives, coverage limits and unresolved boundaries. It does not prove a runtime invariant or guarantee complete caller recall.

The current source, documentation, CI, and contributor hooks are on the [dev branch](https://github.com/dominicnunez/contract-tracer/tree/dev).
## Requirements

- Go 1.27.0 or newer, as selected by the module's `go` directive.
- Dependencies declared in `go.mod`; Go package loading may resolve target-module dependencies using the normal Go toolchain.

The target is analyzed, not executed as an application.

## Install

Install the command-line tool with Go 1.27 or newer:

```sh
go install github.com/dominicnunez/contract-tracer/cmd/contract-trace@latest
```

The library module is `github.com/dominicnunez/contract-tracer` and its Go package name is `contracttrace`.

## Build and test

```sh
go test ./...
go build -trimpath -buildvcs=false -o contract-trace ./cmd/contract-trace
```

On Windows, use `-o contract-trace.exe`. For byte-identical builds in the same environment, use the same Go toolchain and build flags. This command was verified with native Go 1.27.1 from two independent fresh source copies using shared Go caches; it is not a cold-cache, cross-toolchain or clean-machine reproducibility guarantee.

## Run an investigation

```sh
./contract-trace \
-root ./testdata/sample \
-seed Validate \
-invariant 'all accepted values are validated' \
-depth 3 -max-nodes 100 \
-output scope.json
```

On Windows, run `.\contract-trace.exe` from PowerShell. Seeds may be function names, method names, fully qualified function IDs, or discovered resource IDs. `-location file.go:line` seeds an indexed function, global or field declaration. Multiple seeds or locations are comma-separated. Use `-focus` to name symbols or resources that belong to the hypothesis; focus explains graph paths and does not remove structural scope.

Important options include `-tests`, `-tags`, `-expand-callbacks`, `-timeout`, `-format json|markdown`, `-save-analysis file.json.gz`, and `-resume file.json.gz`. The default traversal limits are depth 3 and 250 nodes. Reports retain truncation, frontiers, candidate inventories and unresolved boundaries when analysis is bounded. Saved exploration reuses the discovered graph after checking source and build identities, including the selected Go source-file set and root-local embedded asset matches in selected target packages under the saved tests, tags and Go environment; it does not rerun analysis to recover omitted relationships. Saved analyses created before loaded-source inventory was recorded must be regenerated before exploration.

On Windows, `-output` and `-save-analysis` destinations must use ordinary file names without a trailing dot, trailing space or colon. These forms can alias another file or select an alternate data stream.

An explicit `-config` file must not be replaced by `-save-analysis` or `-output`, including report-only runs. A report-only `-output` cannot replace an existing analyzed input, including root Go and SQL files, `go.mod` and `go.sum`, captured embedded assets, loaded Go sources, or module-resolution inputs. It may create a new source-shaped path such as `report.go`. When saving or resuming an analysis, the snapshot destination and any paired `-output` destination must not replace a captured input or add a fingerprinted source file under the analyzed root. JSON or gzip snapshots and reports may be placed inside the root unless their paths alias captured inputs. The check uses the filesystem state at publication time and assumes the analyzed files and directory layout remain stable while the command runs; it is not a concurrent filesystem-write lock.

## Configuration

`-config config.json` accepts one strict JSON object. Unknown fields, invalid selectors and unsupported rule kinds are errors. Omitted fields keep defaults; explicitly setting an array to `[]` disables that default list. Call and lifecycle symbols use `<Go import path>::<declared function or method>`. A storage scope's `database_origins` may also use `<Go import path>::(package init)` for a database constructor declared in a package-level variable initializer.

Each `-root` selects one Go module tree. SQL inventory stops at a nested directory containing another `go.mod`, even if a configured `sql_files` glob matches files below it. Analyze a nested module separately by selecting its directory as `-root`.

Configured call rules describe typed SQL or event API signatures. Argument indexes exclude a method receiver; `handler_argument` is valid for `event_subscribe`.

```json
{
"call_rules": [
{"symbol":"example.com/service::Publish","kind":"event_publish","argument":1,"namespace":"orders"},
{"symbol":"example.com/service::Subscribe","kind":"event_subscribe","argument":0,"handler_argument":1,"namespace":"orders"},
{"symbol":"example.com/store::Query","kind":"sql_query","argument":0,"namespace":"orders"}
],
"storage_scopes": [
{"namespace":"orders","go_files":["internal/orders/**/*.go"],"sql_files":["migrations/orders/**/*.sql"]}
],
"lifecycle_rules": [
{"symbol":"example.com/service::Acquire","role":"acquire","namespace":"worker","identity":"origin","result":0},
{"symbol":"example.com/service::Release","role":"release","namespace":"worker","identity":"origin","argument":0}
]
}
```

Lifecycle rules map one typed selector (`argument`, `result`, or `receiver`) to a role and namespace. `identity` is `origin` for modeled handle candidates or `value` for string keys. These mappings describe configured structural API roles; they do not prove success, ownership or ordering. `profiles/go-core.json` is an example that disables default event-field, lifecycle-name and SQL-method heuristics.

## Output and limits

JSON reports include nodes, source evidence, relationships, coverage, boundaries and `contract_complete: false`. Markdown is a summary. Saved analysis can be written as JSON or gzip JSON using a `.gz` suffix; resume validates the saved graph and source/build inputs before exploring it with new seeds, locations, focus or budgets. Build identity includes Go architecture feature settings, `GOEXPERIMENT` and `GOFIPS140`; snapshots missing those settings or reopened under a different setting must be regenerated.

Markdown treats report values as literal text. It escapes structural punctuation and shows line breaks and control characters visibly; use JSON when exact text or complete evidence is needed.

The analyzer uses typed Go syntax/SSA and bounded value-flow models. Interface and callback targets may be possible candidates. Reflection, unsafe/cgo, unavailable dependency bodies and unselected build configurations are not fully modeled. Candidate flow is context-insensitive and bounded; it does not establish runtime object identity, invocation pairing, path feasibility, SQL transaction success, event delivery, cleanup success, goroutine scheduling or eventual termination. SQLite storage analysis is built in; other storage systems require configured adapters and their semantics remain unresolved.

SQL file symlinks are not supported. A `.sql` symlink found during source fingerprinting or SQL discovery is rejected, even when a SQL file selector would otherwise exclude it or the file is under a nested module. Source fingerprinting remains conservative across the selected directory tree, excluding `.git`, `vendor` and `.gograph`, and includes descendant Go files, SQL files and `go.mod`/`go.sum` manifests. Changes to nested-module files can therefore invalidate a saved analysis even though nested SQL is outside that root's SQL inventory. Go sources loaded from workspaces or replacements and their module inputs are also checked through separate source and resolution identities. File reads used for fingerprinting and SQL inventory are rooted under the analysis directory; this does not sandbox Go package loading or the Go toolchain.

## Contributing

Install Go and Python 3.12 or newer, then install the pinned development hook runner and enable the commit and push checks:

```sh
python -m pip install -r requirements-dev.txt
python tools/install_hooks.py
```

The installer keeps an existing pre-commit-managed commit hook, installs the standard commit checks, and installs a raw Git pre-push hook. It refuses to replace unrecognized hooks; move or explicitly chain a custom hook before installing these checks. Run the installer again after changing the Python interpreter used by the repository.

Commit checks validate YAML/JSON, whitespace, merge markers and private-key markers, lint GitHub Actions workflows, and verify that staged production Go files already pass `gofmt`. Before running push checks, the raw hook examines every Git ref update. Every non-deletion update must resolve to the checked-out `HEAD` commit, and the worktree must be clean. This permits annotated or lightweight tags that resolve to `HEAD`; tags pointing elsewhere and refs that do not resolve to a commit are rejected. Deletion-only pushes skip validation. Once all updates pass these checks, the hook runs `go vet ./...`, `go test ./... -count=1`, and `go build ./cmd/contract-trace` once. CI runs the Go checks on Linux and Windows with Go 1.27, and runs the repository-wide pre-commit checks and hook-helper tests.

Use [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) for commit messages and squash titles, such as `fix(storage): preserve rollback edges`. Mark a breaking change with `!` after the type or scope (for example, `feat(api)!: change the config format`) or a `BREAKING CHANGE:` footer.
Loading
Loading
0