Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ jobs:
pkg=github.com/MMinasyan/lightcode/internal/version
$(go env GOPATH)/bin/wails build -tags webkit2_41 -ldflags "-X ${pkg}.Version=${TAG} -X ${pkg}.Commit=${commit}"

- name: Protocol freshness
run: make check-protocol

- name: Check Wails bindings
run: make check-wails-bindings

- name: Check formatting
run: |
gofmt -l . | tee /tmp/gofmt-out
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
- name: Build Wails app
run: $(go env GOPATH)/bin/wails build -tags webkit2_41

- name: Check Wails bindings
run: make check-wails-bindings

- name: Vet
run: go vet -tags webkit2_41 ./...

Expand Down
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ VERSION_PKG := github.com/MMinasyan/lightcode/internal/version
LDFLAGS := -X $(VERSION_PKG).Version=$(VERSION) -X $(VERSION_PKG).Commit=$(COMMIT)

build:
~/go/bin/wails build -ldflags '$(LDFLAGS)'
~/go/bin/wails build -tags webkit2_41 -ldflags '$(LDFLAGS)'

test:
go test ./...
Expand Down Expand Up @@ -58,10 +58,17 @@ uninstall:

generate-protocol:
go tool oapi-codegen --config protocol/oapi-codegen.yaml -o protocol/protocol.gen.go protocol/openapi.yaml
go run ./internal/protocolgen -schema protocol/openapi.yaml -out internal/clientops/operations.gen.go -native-contract-out frontend/src/generated/native-invocations.json
cd frontend && npm run generate-protocol

check-protocol: generate-protocol
sh scripts/check-protocol-generated.sh
sh scripts/check-generated-files.sh protocol/protocol.gen.go frontend/src/generated/protocol internal/clientops/operations.gen.go frontend/src/generated/native-invocations.json
cd frontend && npm run typecheck:protocol

.PHONY: build test test-race test-integration bench fuzz-short install-lint-tools lint install uninstall generate-protocol check-protocol
# Compares the Wails-generated Go bindings against the index. Prerequisite:
# the real Wails build (which runs the models.ts normalizer) has produced the
# tracked files under frontend/client-bindings/wailsjs/go.
check-wails-bindings:
sh scripts/check-generated-files.sh frontend/client-bindings/wailsjs/go

.PHONY: build test test-race test-integration bench fuzz-short install-lint-tools lint install uninstall generate-protocol check-protocol check-wails-bindings
41 changes: 40 additions & 1 deletion agent/public_isolation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ const tiktokenPkg = "github.com/pkoukk/tiktoken-go"
// and client.
const oapiRuntimePkg = "github.com/oapi-codegen/runtime"

// kinOpenAPIPkg is the one third-party dependency the private protocol build
// command permits: the pinned schema loader that derives the native operation
// bindings and their invocation contract from the committed schema.
const kinOpenAPIPkg = "github.com/getkin/kin-openapi/openapi3"

// retiredPackageRoots is the one uniform rejection rule for the retired legacy
// package roots selected by this modernization: no tracked production file may
// live under one of these internal package trees or import one. The list is not
Expand Down Expand Up @@ -296,6 +301,10 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str
if imp != clientPkg && imp != protocolPkg && !std[imp] {
problems = append(problems, rel+": internal/clientops imports "+imp+"; the shared typed invocation helper may import only the standard library, "+clientPkg+", and "+protocolPkg)
}
case "internal/protocolgen":
if imp != kinOpenAPIPkg && !std[imp] {
problems = append(problems, rel+": internal/protocolgen imports "+imp+"; the private protocol build command may import only the standard library and "+kinOpenAPIPkg)
}
case "internal/acpclient":
if imp != clientPkg && imp != protocolPkg && imp != clienthostPkg && imp != clientopsPkg && imp != versionPkg && !std[imp] {
problems = append(problems, rel+": internal/acpclient imports "+imp+"; the target ACP bridge may import only the standard library, "+clientPkg+", "+protocolPkg+", "+clienthostPkg+", "+clientopsPkg+", and the native version helper")
Expand Down Expand Up @@ -326,7 +335,7 @@ func checkTrackedGoFile(rel string, imports []string, std map[string]bool) []str
// TestDependencyRulesRejectNonStdlibDotlessImports proves the allowlists use authoritative standard-library membership, not a dot-in-path shape: the cgo pseudo-import "C" and a dotless path outside the stdlib set fail every package row, while ordinary stdlib imports, the model dependency on agent's side, and internal/storage's dependency on harness still pass. The foundation's own reverse and skipping edges — model or agent reaching harness, any target package reaching internal/storage, and the driver reaching a target package — are each rejected exactly once.
func TestDependencyRulesRejectNonStdlibDotlessImports(t *testing.T) {
std := standardLibraryImports(t)
for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go", "internal/storage/x.go", "internal/toolargs/x.go", "protocol/x.go", "client/x.go", "internal/terminal/x.go", "internal/hostprocess/x.go", "internal/clienthost/x.go", "internal/clientview/x.go", "internal/clientops/x.go", "internal/acpclient/x.go", "internal/cliclient/x.go", "internal/desktopclient/x.go"} {
for _, rel := range []string{"model/x.go", "agent/x.go", "harness/x.go", "internal/storage/x.go", "internal/toolargs/x.go", "protocol/x.go", "client/x.go", "internal/terminal/x.go", "internal/hostprocess/x.go", "internal/clienthost/x.go", "internal/clientview/x.go", "internal/clientops/x.go", "internal/protocolgen/x.go", "internal/acpclient/x.go", "internal/cliclient/x.go", "internal/desktopclient/x.go"} {
for _, imp := range []string{"C", "notreal/pkg"} {
if problems := checkTrackedGoFile(rel, []string{imp}, std); len(problems) != 1 {
t.Errorf("%s importing %q: %d problems, want 1: %v", rel, imp, len(problems), problems)
Expand Down Expand Up @@ -769,6 +778,36 @@ func TestDependencyRulesClientViewIsolation(t *testing.T) {
}
}

// TestDependencyRulesProtocolGenIsolation enforces the private protocol build
// command's exact allowlist: the standard library and the pinned schema loader
// only. The foundation, the Runtime, the target clients and the generated
// contracts are its nearest forbidden siblings, and another subpackage of the
// same loader module is not an allowed edge either.
func TestDependencyRulesProtocolGenIsolation(t *testing.T) {
std := standardLibraryImports(t)
if problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{
"context", "encoding/json", "flag", "fmt", "go/format", "os", "sort", "strconv", "strings", "text/template",
kinOpenAPIPkg,
}, std); len(problems) != 0 {
t.Errorf("allowed internal/protocolgen imports flagged: %v", problems)
}
for _, imp := range []string{
publicModule + "/model", publicModule + "/agent", publicModule + "/harness",
publicModule + "/runtime", publicModule + "/internal/agent", publicModule + "/internal/config",
publicModule + "/internal/storage", sqliteDriverPkg, publicModule + "/internal/plugins",
publicModule + "/internal/terminal", publicModule + "/internal/cli",
publicModule + "/internal/clienthost", publicModule + "/internal/clientview",
publicModule + "/internal/clientops", publicModule + "/internal/desktopclient",
publicModule + "/internal/acpclient", publicModule + "/internal/cliclient",
publicModule + "/client", publicModule + "/protocol",
oapiRuntimePkg, tiktokenPkg, "github.com/getkin/kin-openapi/openapi3filter",
} {
if problems := checkTrackedGoFile("internal/protocolgen/x.go", []string{imp}, std); len(problems) != 1 {
t.Errorf("internal/protocolgen importing %q: %d problems, want 1: %v", imp, len(problems), problems)
}
}
}

// TestDependencyRulesClientOpsIsolation enforces the shared typed invocation
// helper's exact allowlist. The reverse edge is covered by the directory
// matrix.
Expand Down
4 changes: 2 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ func Connect(ctx context.Context, discoveryPath string) (*Client, error) {
if health.JSON200.ProtocolVersion == "" {
return nil, fmt.Errorf("authenticated health carries no protocol version: %w", ErrNoRuntime)
}
if live := health.JSON200; live.ProtocolVersion != protocol.N1 {
if live := health.JSON200; live.ProtocolVersion != protocol.N2 {
return nil, fmt.Errorf("live protocol version %q: %w", live.ProtocolVersion, ErrIncompatibleProtocol)
}
if record.ProtocolVersion != string(protocol.N1) {
if record.ProtocolVersion != string(protocol.N2) {
return nil, fmt.Errorf("discovery protocol version %q disagrees with the authenticated live owner: %w", record.ProtocolVersion, ErrNoRuntime)
}
return &Client{ClientWithResponses: transport, instance: record.InstanceId}, nil
Expand Down
Loading