Skip to content

fix(sync): solution-sdk generates a working client for a solution consuming modules through the authority hop (#1094) - #1095

Merged
antoinetoussaint-byte merged 2 commits into
mainfrom
issue-1094-sync-solution-sdk-cannot-generate-a-solution-s
Oct 27, 2026
Merged

antoinetoussaint-byte merged 2 commits into
mainfrom
issue-1094-sync-solution-sdk-cannot-generate-a-solution-s

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1094.

codefly sync solution-sdk --language go,typescript now generates a working library for a solution that has no agent block and consumes two composed modules, one over connect and one over rest, with no service-dependencies. The Go half builds, the TypeScript half is written, and --check passes on the result.

Updated 2026-10-27: adapted to the authority hop, merged up to main

This branch was 72 commits behind and conflicted in four files. Main has been merged in (97be7c4). There was no rebase and no force-push. Three things changed against the first version:

  • Gap 3's reason is now the hop. Since fix(run): compose modules behind the hop on a Connect-faced host #1188 (aa67530) and fix(gitops): a host receives its module's table less the services that stand in for it; a gateway route carries its proto method's policy #1189 (c791071), a solution calls a consumed module through the authority hop's Connect face, not the host gateway's /v1/<alias> route. The hop routes every module the composition puts behind it (hop/MODULES) from that module's published API catalog, and checks each call against the method's policy. The rule stays the same: no service-dependencies edge for another module's consume. The reason is new: such an edge is a mesh path past the hop, a call no policy is checked on. The code comment, the flag help, the long help, docs/commands.md, docs/design/library-system.md and the tests no longer mention the gateway. Test renamed to TestSyncSolutionSDKAsksNoMeshEdgeForAHopRoutedConsume.
  • Gap 2 follows the hop's one-destination rule. Before, a service exporting protobuf on several endpoints was refused as ambiguous. Now several protobuf entries of one service that share one digest count as one contract, which is how the hop routes a service's gRPC and Connect listeners when they publish one contract. Entries with different digests are still refused, naming each endpoint and its digest.
  • Gap 4's TypeScript half now builds on main's barrel. Main's fix(generate): namespace TypeScript client barrel exports #1162 added a namespaced TypeScript entry point: every facade is exported flat and every proto package as a namespace. That already solves the TS2308 clash that this PR's per-facade src/<facade>/index.ts and the package.json "./*" subpath export were working around, so both are dropped. Two mechanisms for one job is one too many. What stays is the part main does not cover: each entry's bindings get their own directory, src/gen/<facade>/, because two contracts rooted at api.proto would otherwise write the same src/gen/api_pb.ts. The Go half is unchanged: a package per facade, go/<facade>/ over go/gen/<facade>/. It is re-threaded through main's staged generation (generate client --language python: buf/validate unresolved, and a failed run deletes the library's python tree #1163) as stagedLanguage.facadePackages.

The generated client is still the Codefly-generated Connect client of each consumed contract. There is no hand-written REST path.

The four gaps

1. A solution manifest without an agent block (fix, in the CLI; no core change)

runSyncSolutionSDK used to read solution.codefly.yaml with core's manifest.Load. That is the executor manifest loader: it requires agent.kind == codefly:solution, and the manifest's digest and admission bind that identity. This command reads only api.consumes, so it now uses core's manifest.LoadConsumption, the loader core provides for such a reader (pkg/gitops/authority.go already uses it). schema_version and api.consumes are still held strictly.

2. A consume of a rest or connect endpoint (fix)

consumedContract generates a consume from the protobuf contract the hop routes for the consumed service:

  • If the catalog has a protobuf contract for the named endpoint, that contract is used.
  • Otherwise, a rest or connect endpoint uses the service's protobuf contract. Several entries under one digest are one contract; different digests are refused.
  • A rest endpoint of a service with no protobuf contract keeps its own OpenAPI entry.
  • If the catalog has no entry for the endpoint, its API is read from the producer's declaration. An endpoint the producer does not declare is refused by name.

3. The dependency check and mesh edges (fix)

missingServiceDependencies reads api.consumes and asks for an edge only for a consume of the solution's own module. A consume of any other module is called through the authority hop, so neither --check nor --apply-dependencies asks for or adds an edge to it. Existing edges are never removed.

4. The aggregated library with two modules (fix)

ClientLibraryRequest.FacadePackages (set only by sync solution-sdk) works like this:

  • Go: go/<facade>/<facade>_facade.pb.go, imported as <module path>/<facade>, with bindings under go/gen/<facade>/. Each Go facade has its own package clause, so two cannot share go/, and two contracts rooted at api.proto would write the same binding.
  • TypeScript: bindings under src/gen/<facade>/, and the facade stays at src/<facade>_facade.ts. The entry point is main's namespaced barrel, unchanged.
  • Python: unchanged.
  • Two entries that would generate the same facade name are refused before codegen ("set as on one").

generate client (one contract) keeps its layout byte for byte.

Reused, and deliberately not built

  • Reused: core's manifest.LoadConsumption; main's staged GenerateClientLibrary pipeline; main's namespaced TypeScript barrel (writeTypeScriptIndex, fix(generate): namespace TypeScript client barrel exports #1162), in place of this PR's earlier per-facade TypeScript index and subpath export.
  • Not built: no change to core's manifest schema; no new solution-manifest reader; no detection of existing mesh edges; no check that a consumed module is actually listed in hop/MODULES, since that is the environment's configuration and the hop refuses an unrouted call itself.

Tests (each confirmed failing with its fix reverted)

  • Gap 1, TestSyncSolutionSDKReadsAManifestWithoutAnAgent.
  • Gap 2, TestSyncSolutionSDKResolvesTransportEndpointsToTheProtobufContract. It now also covers a published connect entry beside grpc, a rest consume over one contract on both, and two digests refused.
  • Gap 3, TestSyncSolutionSDKAsksNoMeshEdgeForAHopRoutedConsume and TestSyncSolutionSDKApplyDependencies.
  • Gap 4: TestAggregatedGoLibraryGivesEachFacadeItsOwnPackage, TestAggregatedLibraryRefusesTwoEntriesWithOneFacadeName, and the new TestAggregatedTypeScriptLibraryGivesEachEntryItsOwnBindings. In one src/gen/ the second api_pb.ts replaces the first. Under src/gen/<facade>/ both stay, and the root barrel namespaces accounts_v1_pb and records_v1_pb and lists the shared GetRequestSchema in its collision comment. Confirmed failing with the bindings directory reverted.
  • End to end, TestSyncSolutionSDKTwoModulesEndToEnd: real proto companion, --language go,typescript, Go compiled by the command, then --check passes. Run locally on the merged head (darwin/arm64, Docker 29.8.2, CODEFLY_GENERATE_QUALIFY=1): PASS in 50 s.
  • go vet ./cmd/... ./pkg/generators/... clean; golangci-lint run ./cmd/sync/... ./pkg/generators/... 0 issues; go test ./cmd/sync/... ./pkg/generators/... ./cmd pass (docs/cli-reference.md regenerated).

Breaking changes

  • sync solution-sdk --apply-dependencies and --check no longer add or require service-dependencies for consumes of other modules. Those edges are mesh paths past the hop.
  • The sync solution-sdk library layout changes. Go facades move from go/ to go/<facade>/ (import path <module path>/<facade>), and bindings from go/gen/ to go/gen/<facade>/. TypeScript bindings move from src/gen/ to src/gen/<facade>/; the root barrel still exports every facade and every package. A previously generated solution SDK fails --check until it is regenerated. The earlier package.json "./*" hand-edit is no longer needed.
  • generate client output is unchanged.

Not verified

  • No live call through a running hop. The generated Connect client has not been pointed at ${endpoint:authority/hop/http} in a composed run here.
  • tsc was not run on the generated TypeScript of the merged head. The end-to-end test checks the files exist, and main's barrel has its own tsc-backed tests.
  • Not run against a real solution repository or a real generate contracts export.
  • Not checked: a contract shared by two entries through a common, non-upstream proto file. With per-entry binding directories it would be generated twice. Main's TypeScript barrel refuses one package exporting a name from two files, and Go's protobuf registry may reject the duplicate at init.
  • Main still derives host-gateway /v1/<alias> routes and gateway mesh edges from api.consumes (orchestration.HostRouteTargets, used by pkg/gitops/cell.go and mesh_edges.go). This PR does not touch them. Whether they are now dead under the hop is a separate question for that code's owner.

Overlaps

Docs

docs/commands.md (sync solution-sdk: loader, hop-based dependency rule, transport and one-digest resolution, library layout), docs/design/library-system.md (Solution SDKs: the declaration does not drive service-dependencies because of the hop), docs/cli-reference.md (regenerated).

🤖 Generated with Claude Code

…suming modules through the host gateway (#1094)

Four gaps stopped `codefly sync solution-sdk` on any solution that names no
executor and consumes modules over the transports the host gateway serves:

- The manifest is read with core's LoadConsumption, the loader core keeps
  for a reader of api.consumes alone: a solution with no `agent` block is no
  longer refused for lacking a codefly:solution executor nothing runs.
- A consume of a connect or rest endpoint resolves to its service's
  protobuf contract, which a catalog exports under grpc.
- Only a consume of the solution's own module asks for (or, with
  --apply-dependencies, gets) a service-dependencies edge; another module is
  reached through the host gateway, and a direct edge is the mesh path a
  solution must not hold.
- The aggregated library gives each facade a package of its own (Go
  go/<facade>/ with bindings under gen/<facade>/; TypeScript
  src/<facade>/ exported as <package>/<facade>, the root re-exporting the
  facades alone), and refuses two entries generating one facade name.

Covered by a fixture in cmd/sync/testdata/solution-sdk and a test per gap;
the Docker end-to-end builds the Go half, writes the TypeScript half and
passes --check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…through the authority hop (#1094)

Brings the branch up to main (72 commits) and adapts it to the hop:

- Gap 3: a consume of another module needs no service-dependencies entry
  because the solution calls it through the authority hop's Connect face,
  which routes each module behind it from its published API catalog and
  checks every call against the method's policy; an edge to the module would
  be a mesh path past the hop. The rule is unchanged; its reason, the flag
  help and the docs no longer name the host gateway's /v1/<alias> route.
- Gap 2: several protobuf entries of one service under one digest are one
  contract, as the hop routes a service's gRPC and Connect listeners under one
  contract as one destination; different digests are still refused.
- Gap 4 on main's staged generation (#1163) and namespaced TypeScript barrel
  (#1162): Go keeps a package per facade; TypeScript keeps a bindings
  directory per entry (src/gen/<facade>/) under main's entry point, which
  already keeps two contracts' same-named messages apart, so the per-facade
  TypeScript index and the package.json "./*" subpath export are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte antoinetoussaint-byte changed the title fix(sync): solution-sdk generates a working client for a solution consuming modules through the host gateway (#1094) fix(sync): solution-sdk generates a working client for a solution consuming modules through the authority hop (#1094) Oct 27, 2026
@antoinetoussaint-byte
antoinetoussaint-byte added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit e9dd3e0 Oct 27, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sync solution-sdk cannot generate a solution's client: four gaps (phantom agent, rest/connect consumes, mesh-edge check, Go facade collision)

1 participant