From 7340b1e7d66c01297ff6be23eafbf7c7961d8a67 Mon Sep 17 00:00:00 2001 From: Lakshman Patel Date: Wed, 15 Jul 2026 09:17:12 +0530 Subject: [PATCH] feat: add Buf-managed proto schema for breaking-change detection + codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only 3 consumers of this package exist today, all Go, already importing it directly — but the same class of drift this is meant to prevent has already happened once: hawk-sdk-go independently hand-rolled its own ToolResult with a field named tool_call_id, diverging from this repo's tool_use_id, because nothing checked for it. proto/hawk/contracts/v1/*.proto mirrors all 30 exported types across the 7 existing packages. Deliberately additive, not authoritative — the hand-written Go package is unchanged and stays the source of truth for Go consumers; regenerating it from proto risked breaking their JSON wire format for zero benefit (protoc-gen-go doesn't reliably preserve existing json: tags without extra glue). gen/go/ is its own nested Go module specifically so google.golang.org/protobuf never touches the root module's zero-dependency guarantee. Each non-obvious mapping decision (Severity/Risk keep Go's exact iota numeric values instead of the usual _UNSPECIFIED=0 sentinel, since map[Severity]int fields depend on that parity; time.Duration maps to int64 not google.protobuf.Duration, matching Go's actual JSON output) is documented inline in the .proto source. CI gains a proto job: buf lint, buf breaking against main (skips gracefully until this first lands, since main has no prior schema to compare against), and a buf generate smoke test. buf lint/breaking/generate were all run locally against a fresh buf install to confirm the schema is valid and codegen succeeds for Go, Python, and TypeScript before wiring any of this in. Also backfills the CHANGELOG, which had been stale since v0.1.1 — and fixes a real inaccuracy found while doing it: the v0.1.0 entry claimed sessions/ existed at initial release, but git history (git log --diff-filter=A) shows it was actually added in v0.1.2. --- .github/workflows/ci.yml | 29 +++++++++ .gitignore | 5 ++ AGENTS.md | 19 ++++++ CHANGELOG.md | 65 +++++++++++++++++-- CODEOWNERS | 1 + Makefile | 17 ++++- README.md | 35 ++++++++++ buf.gen.yaml | 20 ++++++ buf.yaml | 12 ++++ proto/hawk/contracts/v1/events.proto | 44 +++++++++++++ proto/hawk/contracts/v1/finding.proto | 41 ++++++++++++ proto/hawk/contracts/v1/policy.proto | 45 +++++++++++++ proto/hawk/contracts/v1/review.proto | 88 ++++++++++++++++++++++++++ proto/hawk/contracts/v1/sessions.proto | 69 ++++++++++++++++++++ proto/hawk/contracts/v1/severity.proto | 43 +++++++++++++ proto/hawk/contracts/v1/tool.proto | 29 +++++++++ proto/hawk/contracts/v1/verify.proto | 44 +++++++++++++ 17 files changed, 599 insertions(+), 7 deletions(-) create mode 100644 buf.gen.yaml create mode 100644 buf.yaml create mode 100644 proto/hawk/contracts/v1/events.proto create mode 100644 proto/hawk/contracts/v1/finding.proto create mode 100644 proto/hawk/contracts/v1/policy.proto create mode 100644 proto/hawk/contracts/v1/review.proto create mode 100644 proto/hawk/contracts/v1/sessions.proto create mode 100644 proto/hawk/contracts/v1/severity.proto create mode 100644 proto/hawk/contracts/v1/tool.proto create mode 100644 proto/hawk/contracts/v1/verify.proto diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11f1e4e..2658adb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,6 +153,35 @@ jobs: with: extra_args: --only-verified + proto: + name: proto (lint, breaking, generate) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # buf breaking compares against the main branch's history; a + # shallow checkout wouldn't have those commits available. + fetch-depth: 0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + - name: Install buf and protoc-gen-go + run: | + go install github.com/bufbuild/buf/cmd/buf@v1.71.0 + go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11 + - name: buf lint + run: buf lint + - name: buf breaking (against main) + run: | + if git cat-file -e main:proto 2>/dev/null; then + buf breaking --against '.git#branch=main' + else + echo "::notice::proto/ does not exist on main yet — nothing to compare against (this is expected for the PR that first introduces it); breaking-change detection activates for every PR after this one merges." + fi + - name: buf generate (smoke test — confirm codegen still succeeds) + run: buf generate + build: name: build (${{ matrix.goos }}/${{ matrix.goarch }}) runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 0d81c3b..1809bd1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,11 @@ .gocache/ coverage.out +# Generated code from proto/ — regenerate with `buf generate`. Not +# committed: fully derivable, and no consumer depends on a checked-in copy +# yet (see README's Codegen section). +gen/ + # Go workspace (local dev only) go.work go.work.sum diff --git a/AGENTS.md b/AGENTS.md index 32343e8..894f61b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,8 +19,27 @@ go test ./... # Run tests go vet ./... # Static analysis go mod tidy # Tidy modules make boundaries # Enforce ecosystem boundary rules +make proto # Lint proto/, check breaking changes, regenerate gen/ ``` +## Proto / Cross-Language Schema + +`proto/` mirrors every exported Go type for schema-level breaking-change +detection and Python/TypeScript codegen (`gen/`, not committed — regenerate +with `make proto` / `buf generate`). It is additive, not authoritative: the +hand-written Go package above is still the source of truth for Go +consumers, and `gen/go/` lives in its own nested module +(`gen/go/go.mod`) specifically so pulling in `google.golang.org/protobuf` +there never touches the root module's zero-dependency guarantee. + +**When you change an exported Go type, update the matching `.proto` +message in the same PR.** They are not generated from each other and can +drift silently otherwise. CI's `proto` job (`buf lint` + `buf breaking` +against `main`) will fail the build on an accidental breaking change to the +schema — if the break is intentional, explain why in the PR description and +add a CHANGELOG entry under a new version, same as any other breaking +change to this package. + ## Scope Rules **Allowed:** shared enums, structs, event models, policy/tool contracts diff --git a/CHANGELOG.md b/CHANGELOG.md index ca62530..debf25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.1.0] — 2026-07-05 +### Added -Initial governed release. Establishes `VERSION` as the source of truth and -adds repo governance (`CODEOWNERS`, `Makefile`, boundary guard, engine-grade -CI) matching the rest of the hawk-eco foundation/engine repos. +- `proto/` — a Buf-managed protobuf schema mirroring every exported Go type, + used for schema-level breaking-change detection (`buf breaking`, gated in + CI on every PR once this lands) and for generating Python/TypeScript + clients (`buf generate` → `gen/python/`, `gen/typescript/`; not committed, + regenerate on demand). The hand-written Go package is unaffected and stays + the source of truth for Go consumers — see `proto/README.md` (or the repo + README's Codegen section) for the mapping rules between the two. +- CI: `proto` job runs `buf lint` and `buf breaking` (against `main`) on + every PR that touches `proto/`. -Existing contract packages at this version: +No contract package changes since `v0.1.4` — this release line so far is +CI/tooling and the addition above. + +## [0.1.4] — 2026-07-11 + +### Added + +- `VERSION` file + `//go:embed`-based `contracts.Version`, replacing ad hoc + versioning with a single source of truth (#6). +- CI: race-detector + coverage-threshold gate. + +### Changed + +- `policy.ParseRisk` now uses `strings.ToLower`/`strings.TrimSpace` instead + of a hand-rolled lowercasing helper — internal only, no behavior change. +- Go version bumped to 1.26.5. + +No wire-format / contract-shape changes in this release. + +## [0.1.3] — 2026-07-05 + +Added the MIT `LICENSE` file. No contract changes. + +## [0.1.2] — 2026-07-04 + +### Added + +- **New `sessions/` package**: `Phase`, `SessionID`, `ContextSnapshot`, + `ToolCallRecord`, `PhaseUsage`, `CostAccumulator` — cross-repo agent + session identity, pipeline-phase tagging, and cost accounting, so engines + and hawk's orchestrator share one vocabulary for this without a circular + dependency. +- `CODEOWNERS`, CI governance workflow. + +## [0.1.1] — 2026-06-25 + +### Added + +- `review.SASTFusionResult` (`Confirmed`/`Dismissed`/`Unaddressed` finding + lists) on `review.Result`, populated when SAST-LLM fusion is active (#1). + +## [0.1.0] — 2026-06-21 + +Initial governed release. Establishes repo governance (`Makefile`, boundary +guard, engine-grade CI) matching the rest of the hawk-eco foundation/engine +repos. + +Contract packages at this version — six packages; `sessions/` did not exist +yet (added in `0.1.2`, see above): - `types/` — severity, findings, shared result vocabulary - `tools/` — provider-neutral tool call and tool result contracts @@ -21,4 +75,3 @@ Existing contract packages at this version: - `policy/` — risk, permission verdict, guardian decision, approval request contracts - `review/` — neutral review findings, comments, stats, and result contracts - `verify/` — neutral verification findings, stats, and report contracts -- `sessions/` — cross-repo agent session state types diff --git a/CODEOWNERS b/CODEOWNERS index 5019045..c947d73 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,6 +9,7 @@ /review/ @GrayCodeAI/llm-team /verify/ @GrayCodeAI/llm-team /sessions/ @GrayCodeAI/llm-team +/proto/ @GrayCodeAI/llm-team /VERSION @GrayCodeAI/maintainers # CI / release diff --git a/Makefile b/Makefile index 0ca34c5..dff69ad 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ GOVULNCHECK := $(GOBIN_DIR)/govulncheck # Phony declarations (alphabetical). # --------------------------------------------------------------------------- .PHONY: all boundaries build ci clean cover fmt help hooks lint lint-fix \ - security test test-race tidy version vet + proto security test test-race tidy version vet boundaries: ## Enforce foundation-repo import boundaries (zero GrayCodeAI/* deps). bash ./scripts/check-ecosystem-boundaries.sh @@ -87,6 +87,21 @@ tidy: ## Tidy go.mod / go.sum. go mod tidy go mod verify +# --------------------------------------------------------------------------- +# Proto (cross-language schema — not part of the `ci` composite target, +# since it needs `buf` installed separately; run explicitly or see the +# `proto` job in .github/workflows/ci.yml for what actually gates merges). +# --------------------------------------------------------------------------- +proto: ## Lint proto/, check for breaking changes vs main, and regenerate gen/. + @command -v buf >/dev/null 2>&1 || (echo "install: go install github.com/bufbuild/buf/cmd/buf@v1.71.0" && exit 1) + buf lint + @if git cat-file -e main:proto 2>/dev/null; then \ + buf breaking --against '.git#branch=main'; \ + else \ + echo "proto/ does not exist on main yet — skipping breaking-change check."; \ + fi + buf generate + # --------------------------------------------------------------------------- # Composite gate used by CI and pre-push. # --------------------------------------------------------------------------- diff --git a/README.md b/README.md index e536537..3827637 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,40 @@ Rules that keep this repo at the foundation layer: If a change here would require importing anything outside the standard library, that type does not belong in this repo. +## Codegen + +`proto/hawk/contracts/v1/*.proto` mirrors every exported type above, one +`.proto` file per Go package. It exists for two things the hand-written Go +package alone can't give you: + +- **Schema-level breaking-change detection.** CI's `proto` job runs + `buf breaking` against `main` on every PR — catches the class of bug + already seen once in this ecosystem (`hawk-sdk-go` independently + hand-rolled its own `ToolResult` with a field named `tool_call_id`, + diverging from this repo's `tool_use_id`, because nothing checked for it). +- **Python / TypeScript codegen**, for whenever a non-Go consumer wants this + vocabulary instead of hand-porting it. `buf generate` produces + `gen/go/`, `gen/python/`, `gen/typescript/` — not committed (regenerate + with `make proto`), and not currently imported by anything: `hawk-sdk-go`, + `hawk-sdk-python`, and `yaad`'s TypeScript SDK all still hand-port their + own subset of this vocabulary today (`hawk-sdk-python/src/hawk/sessions.py` + and `types.py` are the main example) and haven't adopted the generated + packages. That's a real adoption decision for those repos to make on + their own schedule, not something this repo forces. + +**The `.proto` files and the Go structs are two independent, hand-kept-in-sync +definitions** — `gen/go/` is deliberately its own nested Go module (see +`gen/go/go.mod`) precisely so depending on `google.golang.org/protobuf` +there never touches this repo's zero-dependency root module. When you add +or change an exported Go type, update the matching `.proto` message in the +same PR — see `AGENTS.md`. + +Some fields don't map 1:1 by protobuf convention; each divergence is +commented in the `.proto` source at the point it occurs (e.g. `Severity`'s +zero value is `SEVERITY_INFO`, not an `_UNSPECIFIED` sentinel, to keep its +numeric values identical to the Go `iota` constants that +`map[Severity]int` fields serialize by). + ## Package Ownership | Path | Team | @@ -122,6 +156,7 @@ library, that type does not belong in this repo. | `/review/` | `@GrayCodeAI/llm-team` | | `/verify/` | `@GrayCodeAI/llm-team` | | `/sessions/` | `@GrayCodeAI/llm-team` | +| `/proto/` | `@GrayCodeAI/llm-team` | | `/VERSION` | `@GrayCodeAI/maintainers` | | `/Makefile` | `@GrayCodeAI/devops-team` | | `/*.md` | `@GrayCodeAI/docs-team` | diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..6ae1d0d --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,20 @@ +version: v2 +plugins: + # Go: generated for potential future opt-in use only — NOT wired into the + # existing hand-written package (types/, tools/, events/, policy/, + # review/, verify/, sessions/), which stays the source of truth for the + # three existing Go consumers (sight, inspect, hawk). See buf.yaml / + # README for why. + - local: protoc-gen-go + out: gen/go + opt: paths=source_relative + # Python and TypeScript: no existing consumers adopt these yet (see + # README) — generated so hawk-sdk-python / yaad's TS SDK can opt in later + # without hawk-core-contracts needing another release. + - remote: buf.build/protocolbuffers/python + out: gen/python + - remote: buf.build/protocolbuffers/pyi + out: gen/python + - remote: buf.build/bufbuild/es + out: gen/typescript + opt: target=ts diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..7f11def --- /dev/null +++ b/buf.yaml @@ -0,0 +1,12 @@ +version: v2 +modules: + - path: proto + name: buf.build/graycodeai/hawk-core-contracts +deps: + - buf.build/protocolbuffers/wellknowntypes +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/proto/hawk/contracts/v1/events.proto b/proto/hawk/contracts/v1/events.proto new file mode 100644 index 0000000..0c04807 --- /dev/null +++ b/proto/hawk/contracts/v1/events.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// ToolEvent mirrors events.ToolEvent (events/events.go) — a normalized +// tool event emitted by Hawk workflows. +message ToolEvent { + string tool_name = 1; + google.protobuf.Struct tool_input = 2; + string cwd = 3; + google.protobuf.Timestamp timestamp = 4; + string session_id = 5; + string transcript = 6; +} + +// TraceEvent mirrors events.TraceEvent — a normalized trace record for +// model/runtime activity. +message TraceEvent { + string id = 1; + string name = 2; + string input = 3; + string output = 4; + string model = 5; + google.protobuf.Timestamp start_time = 6; + google.protobuf.Timestamp end_time = 7; + map metadata = 8; + // Optional in Go (*UsageInfo, omitempty). proto3 message fields are + // inherently optional/presence-tracked, so no extra wrapper is needed. + UsageInfo usage = 9; +} + +// UsageInfo mirrors events.UsageInfo — token and cost information for a +// trace event. +message UsageInfo { + int32 prompt_tokens = 1; + int32 completion_tokens = 2; + int32 total_tokens = 3; + double cost_usd = 4; +} diff --git a/proto/hawk/contracts/v1/finding.proto b/proto/hawk/contracts/v1/finding.proto new file mode 100644 index 0000000..c40a276 --- /dev/null +++ b/proto/hawk/contracts/v1/finding.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "google/protobuf/timestamp.proto"; +import "hawk/contracts/v1/severity.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// Finding mirrors types.Finding (types/finding.go) — a unified analysis +// concern sourced from Hawk support engines. +message Finding { + string id = 1; + string source = 2; + string concern = 3; + Severity severity = 4; + string file = 5; + string url = 6; + int32 line = 7; + int32 end_line = 8; + string message = 9; + string cwe = 10; + double confidence = 11; + string fix = 12; + string reasoning = 13; + repeated string tags = 14; + map metadata = 15; + google.protobuf.Timestamp created_at = 16; +} + +// FindingSummary mirrors types.FindingSummary — aggregate counts over a +// set of findings. by_severity is keyed by Severity.String() (e.g. +// "critical") in Go, since it's built from a map[string]int, not +// map[Severity]int — unlike review.Stats/verify.Stats below, this one +// really is string-keyed on the wire today. +message FindingSummary { + int32 total = 1; + map by_source = 2; + map by_severity = 3; + double avg_confidence = 4; +} diff --git a/proto/hawk/contracts/v1/policy.proto b/proto/hawk/contracts/v1/policy.proto new file mode 100644 index 0000000..074a0ff --- /dev/null +++ b/proto/hawk/contracts/v1/policy.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// Risk mirrors policy.Risk (policy/policy.go) — severity of a permission +// or policy verdict. Like Severity, Go's zero value (Low) is meaningful, +// not a sentinel, so the numeric values are kept identical to the Go iota +// constants (Low=0 .. Blocked=3) rather than inserting an artificial +// _UNSPECIFIED at 0. +enum Risk { + // buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX + RISK_LOW = 0; + RISK_MEDIUM = 1; + RISK_HIGH = 2; + RISK_BLOCKED = 3; +} + +// PermissionVerdict mirrors policy.PermissionVerdict — the unified outcome +// type for permission subsystems. +message PermissionVerdict { + bool allowed = 1; + string reason = 2; + string rule = 3; + Risk risk = 4; + double confidence = 5; + string source = 6; +} + +// GuardianDecision mirrors policy.GuardianDecision — a provider-neutral +// automatic permission review response. +message GuardianDecision { + bool allowed = 1; + string reason = 2; + double confidence = 3; +} + +// PermissionRequest mirrors policy.PermissionRequest — a user-facing +// approval request. +message PermissionRequest { + string tool_name = 1; + string tool_id = 2; + string summary = 3; +} diff --git a/proto/hawk/contracts/v1/review.proto b/proto/hawk/contracts/v1/review.proto new file mode 100644 index 0000000..7ea3700 --- /dev/null +++ b/proto/hawk/contracts/v1/review.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "hawk/contracts/v1/severity.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// ReviewFinding mirrors review.Finding (review/review.go) — the neutral +// review finding contract shared across Hawk and review engines. Named +// ReviewFinding here (proto has one flat message namespace per package; +// Go disambiguates by package instead) — distinct from Finding +// (finding.proto, mirrors types.Finding) and VerifyFinding (verify.proto): +// these are three genuinely different field sets in Go today, not one +// shape, so they stay three separate messages here too. +message ReviewFinding { + string concern = 1; + Severity severity = 2; + string file = 3; + int32 line = 4; + int32 end_line = 5; + string message = 6; + string fix = 7; + string reasoning = 8; + string cwe = 9; + double confidence = 10; + bool sast_source = 11; +} + +// ReviewInlineComment mirrors review.InlineComment — a review finding +// mapped to a concrete diff position. +message ReviewInlineComment { + string path = 1; + int32 start_line = 2; + int32 end_line = 3; + string body = 4; + string suggestion = 5; +} + +// ReviewStats mirrors review.Stats — review execution metrics. by_severity +// is map keyed by the Severity numeric value (Go's +// map[contracts.Severity]int marshals to integer-string JSON keys, not +// severity names — see severity.proto's comment on Severity's numbering). +// duration_per_concern is int64 nanoseconds per key, matching Go's default +// JSON marshaling of time.Duration (a plain int64, not +// google.protobuf.Duration's "3.000001s"-style string). +message ReviewStats { + int32 files_reviewed = 1; + int32 hunks_analyzed = 2; + int32 findings_total = 3; + map by_severity = 4; + map by_concern = 5; + int32 tokens_used = 6; + map duration_per_concern = 7; + double average_confidence = 8; + int32 high_confidence_count = 9; + int32 low_confidence_count = 10; +} + +// ReviewConfidenceBreakdown mirrors review.ConfidenceBreakdown — review +// findings grouped by confidence band. +message ReviewConfidenceBreakdown { + repeated ReviewFinding high = 1; + repeated ReviewFinding medium = 2; + repeated ReviewFinding low = 3; +} + +// ReviewSASTFusionResult mirrors review.SASTFusionResult — tracks how the +// LLM handled SAST findings during a review. Only populated in Go when +// SAST-LLM fusion is active. +message ReviewSASTFusionResult { + repeated ReviewFinding confirmed = 1; + repeated ReviewFinding dismissed = 2; + repeated ReviewFinding unaddressed = 3; +} + +// ReviewResult mirrors review.Result — the neutral review result contract. +message ReviewResult { + repeated ReviewFinding findings = 1; + repeated ReviewInlineComment comments = 2; + ReviewStats stats = 3; + string report = 4; + Severity fail_on = 5; + // Optional in Go (*SASTFusionResult, omitempty). + ReviewSASTFusionResult sast_fusion = 6; + // Optional in Go (*ConfidenceBreakdown, omitempty). + ReviewConfidenceBreakdown confidence_breakdown = 7; +} diff --git a/proto/hawk/contracts/v1/sessions.proto b/proto/hawk/contracts/v1/sessions.proto new file mode 100644 index 0000000..53adad3 --- /dev/null +++ b/proto/hawk/contracts/v1/sessions.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// Phase mirrors sessions.Phase (sessions/sessions.go) — the pipeline stage +// that generated a token-usage event. Go represents this as a STRING enum +// (type Phase string) whose zero value ("") already means "unknown", so +// PHASE_UNSPECIFIED=0 matches Go's own PhaseUnknown semantics exactly — +// no numeric-parity conflict here the way Severity/Risk have, since Go's +// Phase was never int-backed to begin with. +enum Phase { + PHASE_UNSPECIFIED = 0; + PHASE_LOCALIZE = 1; + PHASE_REPAIR = 2; + PHASE_VALIDATE = 3; + PHASE_REVIEW = 4; + PHASE_PLANNING = 5; +} + +// ContextSnapshot mirrors sessions.ContextSnapshot — a point-in-time, +// compressed view of an agent's working memory. +message ContextSnapshot { + string session_id = 1; + Phase phase = 2; + int32 token_count = 3; + google.protobuf.Timestamp compressed_at = 4; + string summary = 5; + repeated string anchors = 6; +} + +// ToolCallRecord mirrors sessions.ToolCallRecord — a single tool +// invocation within a session, tagged with its pipeline phase. +message ToolCallRecord { + string session_id = 1; + Phase phase = 2; + string tool_name = 3; + int32 input_tokens = 4; + int32 output_tokens = 5; + int64 duration_ms = 6; + string error = 7; + google.protobuf.Timestamp timestamp = 8; +} + +// PhaseUsage mirrors sessions.PhaseUsage — token and cost usage for a +// single pipeline phase. +message PhaseUsage { + int32 input_tokens = 1; + int32 output_tokens = 2; + int32 total_tokens = 3; + double cost_usd = 4; +} + +// CostAccumulator mirrors sessions.CostAccumulator — token/cost tracking +// across all pipeline phases within a session. by_phase is +// map keyed by the Phase's actual string value (e.g. +// "localize") — unlike Severity (an int-kind Go type, see severity.proto), +// Phase's Kind is string, so Go's encoding/json already uses the phase +// name itself as the map key, not a numeric index. This is the one map in +// this schema that genuinely is string-keyed on the wire today. +message CostAccumulator { + string session_id = 1; + map by_phase = 2; + int32 total_tokens = 3; + double total_cost_usd = 4; +} diff --git a/proto/hawk/contracts/v1/severity.proto b/proto/hawk/contracts/v1/severity.proto new file mode 100644 index 0000000..06aa620 --- /dev/null +++ b/proto/hawk/contracts/v1/severity.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// Severity represents the impact level of a finding. Mirrors types.Severity +// (types/severity.go) exactly: this is an int-backed Go enum whose zero +// value ("info") is itself meaningful, not a sentinel — so unlike most +// proto3 enums, the first value here is SEVERITY_INFO, not an artificial +// _UNSPECIFIED, to keep the numeric values identical to the Go iota +// constants (Info=0 .. Critical=4). This matters because +// review.Stats.by_severity / verify.Stats.by_severity are +// map keyed by this exact numeric value, matching what Go's +// encoding/json already emits for a map keyed by a non-string int type. +enum Severity { + // buf:lint:ignore ENUM_ZERO_VALUE_SUFFIX + SEVERITY_INFO = 0; + SEVERITY_LOW = 1; + SEVERITY_MEDIUM = 2; + SEVERITY_HIGH = 3; + SEVERITY_CRITICAL = 4; +} + +// TokenSeverity mirrors types.TokenSeverity (compression/token error rule +// severity). Go represents this as a string enum with no defined zero +// value; proto3 requires one, so TOKEN_SEVERITY_UNSPECIFIED=0 is added. +enum TokenSeverity { + TOKEN_SEVERITY_UNSPECIFIED = 0; + TOKEN_SEVERITY_LOW = 1; + TOKEN_SEVERITY_MEDIUM = 2; + TOKEN_SEVERITY_HIGH = 3; + TOKEN_SEVERITY_CRITICAL = 4; +} + +// AuditSeverity mirrors types.AuditSeverity (security audit finding +// severity; Go values are uppercase strings CRITICAL/WARNING/INFO). +enum AuditSeverity { + AUDIT_SEVERITY_UNSPECIFIED = 0; + AUDIT_SEVERITY_INFO = 1; + AUDIT_SEVERITY_WARNING = 2; + AUDIT_SEVERITY_CRITICAL = 3; +} diff --git a/proto/hawk/contracts/v1/tool.proto b/proto/hawk/contracts/v1/tool.proto new file mode 100644 index 0000000..e3e7f63 --- /dev/null +++ b/proto/hawk/contracts/v1/tool.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "google/protobuf/struct.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// ToolCall mirrors tools.ToolCall (tools/tool.go) — a provider-neutral +// tool invocation contract. arguments is arbitrary JSON in Go +// (map[string]interface{}); google.protobuf.Struct is the standard proto +// representation of an arbitrary JSON object. +message ToolCall { + string id = 1; + string name = 2; + google.protobuf.Struct arguments = 3; +} + +// ToolResult mirrors tools.ToolResult — a provider-neutral tool execution +// result contract. Field name is deliberately tool_use_id (not +// tool_call_id): hawk-sdk-go independently hand-rolled its own ToolResult +// with tool_call_id, which is exactly the kind of naming drift this schema +// (and buf breaking) exists to catch and prevent going forward — this +// proto is authoritative for the tool_use_id spelling. +message ToolResult { + string tool_use_id = 1; + string content = 2; + bool is_error = 3; +} diff --git a/proto/hawk/contracts/v1/verify.proto b/proto/hawk/contracts/v1/verify.proto new file mode 100644 index 0000000..fdcdf1a --- /dev/null +++ b/proto/hawk/contracts/v1/verify.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package hawk.contracts.v1; + +import "hawk/contracts/v1/severity.proto"; + +option go_package = "github.com/GrayCodeAI/hawk-core-contracts/gen/go/hawk/contracts/v1;contractsv1"; + +// VerifyFinding mirrors verify.Finding (verify/verify.go) — the neutral +// verification finding contract shared across Hawk and verification +// engines. See review.proto's ReviewFinding comment for why this is a +// separate message from Finding/ReviewFinding rather than a shared shape. +message VerifyFinding { + string check = 1; + Severity severity = 2; + string url = 3; + string element = 4; + string message = 5; + string fix = 6; + string evidence = 7; +} + +// VerifyStats mirrors verify.Stats — verification execution metrics. +// Same map-key and duration encoding rationale as review.proto's +// ReviewStats. +message VerifyStats { + int32 pages_scanned = 1; + int32 findings_total = 2; + map by_severity = 3; + map by_check = 4; + map duration_per_check = 5; +} + +// VerifyReport mirrors verify.Report — the neutral verification report +// contract. duration is int64 nanoseconds, matching Go's default JSON +// marshaling of time.Duration. +message VerifyReport { + string target = 1; + repeated VerifyFinding findings = 2; + VerifyStats stats = 3; + int32 crawled_urls = 4; + int64 duration = 5; + Severity fail_on = 6; +}