seatgen: a client may import its contracts module's domain SDK (-domain-sdk; ADR 0634 §2; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 14:41:28 +01:00
parent 75cf83a656
commit 9017edaf9e
4 changed files with 19 additions and 1 deletions
@@ -15,6 +15,7 @@ func main() {
check := flag.Bool("check", false, "compare instead of writing: name every path out of step")
goModule := flag.String("go-module", "", "the generated Go tree's module path, such as git.novox.be/novox/mesh-contracts/<folder>/go (there is no root module)")
tsPackage := flag.String("ts-package", "", "the generated TypeScript package's name, such as @novox/mesh-contracts-<folder>")
domainSDK := flag.String("domain-sdk", "", "the Go module of the contracts module's domain SDK, which a client may import (ADR 0634 §2); empty when it has none")
flag.Parse()
dir := flag.Arg(0)
m, err := seatgen.Load(dir, *goModule, *tsPackage)
@@ -22,6 +23,7 @@ func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
m.DomainSDK = *domainSDK
files, err := seatgen.Generate(m)
if err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -33,7 +33,7 @@ var toolAddress = re.MustCompile(`^[a-z][a-z0-9-]*\.[a-z0-9]+_[a-z0-9_]+$`)
// CheckClients is ADR 0627 §3's catalog check of the clients, and ADR 0625 §5's rule for the files beside them: every
// directory under ClientRoot is the client of one seat defined whole, a Go module of its own at ClientGoModule; it
// imports only the standard library, its own seat's contract module and mesh-sdk's runtime (never another seat's
// imports only the standard library, its own seat's contract module, mesh-sdk's runtime and its domain's SDK (never another seat's
// contract or client); and no string in it names a module's subject (`mesh.mod.`), a module's tool, or another seat
// (its name, its subjects, its `seat:` call). Each problem is named once, sorted.
func CheckClients(dir string, m *Module) ([]string, error) {
@@ -111,6 +111,8 @@ func checkClient(dir, at, seat string, m *Module) ([]string, error) {
switch {
case !strings.Contains(strings.SplitN(path, "/", 2)[0], "."):
case path == own:
case m.DomainSDK != "" && (path == m.DomainSDK || strings.HasPrefix(path, m.DomainSDK+"/")):
// the domain's SDK (ADR 0634 §2)
case path == RuntimeModule || strings.HasPrefix(path, RuntimeModule+"/"):
case path == ClientGoModule(m.GoModule, seat) || strings.HasPrefix(path, ClientGoModule(m.GoModule, seat)+"/"):
// the client's own packages: a client may be several packages of its module
@@ -14,6 +14,7 @@ func TestAClientUsesOnlyItsSeatAndTheRuntime(t *testing.T) {
if err != nil {
t.Fatal(err)
}
m.DomainSDK = "example.org/domain/sdk"
files, err := Generate(m)
if err != nil {
t.Fatal(err)
@@ -68,6 +69,16 @@ import "example.org/contracts/go/clients/aregistryclient/provider"
var _ = provider.X
`,
}, artifacts(clientArtifact), nil},
{"a client importing its domain's SDK, which another module's is not", map[string]string{
"go/clients/aregistryclient/go.mod": "module example.org/contracts/go/clients/aregistryclient\n",
"go/clients/aregistryclient/client.go": `package aregistryclient
import (
"example.org/domain/sdk/reference"
"example.org/other/sdk/reference"
)
`,
}, artifacts(clientArtifact), []string{`client.go: imports example.org/other/sdk/reference: a client uses only`}},
{"a client of no seat defined whole, and one not a module of its own", map[string]string{
"go/clients/onestoreclient/x.go": "package onestoreclient\n",
"go/clients/aregistryclient/go.mod": "module example.org/contracts/go\n",
@@ -21,6 +21,9 @@ type Module struct {
GoModule string
// TSPackage is the npm package's name.
TSPackage string
// DomainSDK is the Go module path of the contracts module's domain SDK (ADR 0634 §2), which a seat's client may
// import beside its contract and mesh-sdk's runtime; empty when the domain has none.
DomainSDK string
// Order is every seat's name, sorted: a contracts module has no seat list and no reading order (ADR 0635 §1).
Order []string
// Seats are every definition, resolved, by name.