Skip to content

feat(gitops): derive a composed solution's authority document from its scoped api.consumes - #972

Merged
antoinetoussaint-byte merged 6 commits into
mainfrom
solution-consumption-authority
Oct 12, 2026
Merged

antoinetoussaint-byte merged 6 commits into
mainfrom
solution-consumption-authority

Conversation

@antoinetoussaint-byte

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

Copy link
Copy Markdown
Contributor

The delivery half of codefly-dev/module-saas-starter#1083, rendered. Depends on codefly-dev/core#784. core#784 is merged; this branch pins its merge commit on core main as a pseudo-version (v0.19.2-0.20261012184621-7ab1214c1f03, 7ab1214c), whose tree is identical to the PR head reviewed. The host half is codefly-dev/module-saas-starter#1084.

What changes

pkg/gitops/authority.go: a module's authority request is a module contract either way. It is the contract the module publishes, or, for a composed solution, the one its scoped api.consumes amounts to (core's ConsumptionContract). Both go through the same Resolve against the composition, so a solution's audience is whatever the composition says that module verifies, exactly as for a module.

  • The narrow read (B1). The solution manifest is read through core's LoadConsumption: the schema version and api.consumes, strictly, and nothing else. An invalid or unknown field in a consumption fails the render. A section the strict loader refuses for reasons unrelated to consumption, such as no agent block or a section core doesn't model, never fails the render of a solution that asks for no authority. Those solutions render exactly as before this PR; none of them needs a new tag. The run path's lenient read is unchanged.
  • Refused at render, where the author reads it:
    • a scoped entry for a module the workspace does not compose;
    • one addressed to the solution itself, either by naming its own module or by a resolved audience equal to its principal. The host omits such a binding with only a logged reason.
  • A contract plus scoped consumption is refused by name, including a contract that declares only destinations. Merging two declarations of one request is a decision nobody has made yet. Until then, a solution declares its consumption in one place.
  • Unchanged: one module-identity service presents the request; a presence is required, and the no-presence path refuses either source; the principal is the composed name; the unit is <presence binding>:<entry id>:invoke.

Classification: fix, at the owner. The renderer consumes core's derivation and resolution, and reimplements neither.

Tests

pkg/gitops/solution_consumption_authority_test.go, with structural fixtures in testdata/consumption/:

  • end to end, a solution renders one documents unit with its composition-resolved audience;
  • the scoped-consumer fixture renders deterministically (byte-identical twice), from the reviewer's case;
  • the audience follows the composition: model resolves to model-gateway, from the reviewer's case;
  • unscoped solutions that Load refuses still render (agentless, undeclared section), from the reviewer's B1 case;
  • no scopes means no authority;
  • a module that isn't composed is refused (reviewer);
  • a scope at itself is refused, by module and by resolved audience (reviewer);
  • a contract plus consumption is refused, including destinations-only (the reviewer's case, inverted per the coordinator's decision);
  • an invalid scope fails;
  • a solution without module-identity is refused;
  • two solutions in one composition get distinct units (reviewer).

Mutation: putting back the strict Load fails both B1 subtests. pkg/solutionrun's test compares ConsumedAPI with reflect.DeepEqual, because core#784 made it non-comparable.

Docs updated

docs/solution-host-bindings.md: the record table and the composed-solution section.

Verified, and not

  • Green: go build ./...; go vet ./pkg/gitops ./pkg/solutionrun; golangci-lint run ./pkg/gitops/... ./pkg/solutionrun/... (0 issues, after fixing two gocritic rangeValCopy findings CI reported); go test ./pkg/gitops (full package), ./pkg/solutionrun, ./cmd/sync/....
  • The conformance matrix declares the v0.19.2 line (pkg/conformance/matrix.json core, decided by the coordinator). TestMatrixCoreMatchesGoMod requires it to equal the release line of go.mod's pin, and the pin is a pseudo-version on v0.19.2. No generator exists for this field. The repository's documented procedure (docs/supported-matrix.md, "Updating the core pin") edits it in the same change, and I followed it. The line has no release yet. docs/supported-matrix.md now states that the pin is an unreleased main commit, and that the matrix declares a line ahead of its release until core v0.19.2 is cut. go test ./pkg/conformance/... ./pkg/gitops/... passes.
  • Not run end to end: no render of a real composition, no publish, and no host admitting the document.

What a solution needs to reach the host's ceiling:

  • schema v1, with audience and scopes on the entries it calls on a person's behalf;
  • module-identity: true on the service that presents it;
  • an envelope entry on the host for its principal, with workload identity and units;
  • an organization installation made after that authority is delivered.

🤖 Generated with Claude Code

…s scoped api.consumes

The render derived an authority document only from module.contract.codefly.yaml.
A composed solution declares what it consumes in solution.codefly.yaml and
never in a contract, so it got no authority document, and a host that bounds
a delegated person's capability by the consumer's declaration had nothing
for it.

A module's authority request now comes from exactly one source: its contract,
or its solution manifest's scoped api.consumes, derived through core's
Manifest.ConsumptionAuthority. Each scoped entry becomes an invoke unit
addressed to the consumed module. The manifest is read strictly here, unlike
the run path's lenient read, because an authority document is derived from
it. Asking from both sources is refused. The existing rules apply unchanged:
one module-identity service, a presence, and the solution's composed name as
its principal. A solution that asks for scopes with no presence is refused
instead of being dropped.

Pins core at codefly-dev/core#784's commit until that is tagged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
antoinetoussaint-byte and others added 5 commits October 12, 2026 14:03
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…esolve its audience like a contract

From review:

- The render reads a solution manifest through core's LoadConsumption: the
  schema version and api.consumes, strictly, and nothing else. A solution
  whose manifest the strict loader refuses for an unrelated section (no agent
  block, a section core does not model), and which asks for no authority,
  renders as before.
- A solution's consumption is a module contract (core's
  ConsumptionContract), resolved through the same Resolve as a published one.
  Its audience is the composition's, not the composed module name.
- The render refuses a scoped entry for a module the workspace does not
  compose, and one addressed to the solution itself, by module or by a
  resolved audience equal to its principal. The host could never install
  either.
- A contract plus scoped consumption is still refused by name, including a
  destinations-only contract, until merging the two is decided.
- Fixtures use structural names. Adds the review's tests.

Pins core at codefly-dev/core#784's head as a pseudo-version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PR-head pseudo-version named a commit the squash merge does not keep.
This one is the commit core main carries, with an identical tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
go.mod pins core main at codefly-dev/core#784 by pseudo-version, which is the
v0.19.2 release line, so the matrix declares that line in the same change
(docs/supported-matrix.md, "Updating the core pin"). The line has no release
yet, and the docs say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

1 participant