Repository navigation
feat(solution/manifest): a consumed API declares the scopes it asks for, and derives the solution's authority request - #784
Merged
Conversation
…or, and derives the solution's authority request
A composed solution is a module, but it declares what it consumes in
solution.codefly.yaml's api.consumes, and never in a module contract.
Nothing derived an authority request from that declaration. So a solution
held no authority document and no registry grant, and a host that bounds a
person's capability by what the consumer declares had nothing to read for it.
An api.consumes entry may now carry `scopes`: the {resource_kind, actions}
the solution asks for at the consumed module when it calls it on a person's
behalf. They are validated to the module contract's grammar, refused on
exposes and on unbound entries, and kept canonical in the digest. A scoped
entry whose `as` names another audience than its module is refused, because
a runtime addresses the module by `as` and the binding by its name.
Manifest.ConsumptionAuthority(principal) derives the same
modulecontract.Resolved a contract resolves to. Each scoped entry becomes one
invoke binding with the entry's ID, addressed to the consumed module's name,
and bounded by exactly the declared scopes. Namespaces, queues and scope
ceilings are empty, not absent. An entry without scopes asks for nothing, and
a solution with none returns nil.
docs/solution-host-binding.md describes it beside the module contract.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…act, and read only api.consumes for authority
From review:
- The audience is a slot ({from: group/key}) on a scoped entry, resolved by
the module contract's Resolve, because the audience a module verifies is
the composition's to say. The rule tying `as` to the module is gone.
- ConsumptionContract builds a module contract, so the contract's rules hold
the request's grammar (kinds, actions, slot key meaning, revision) and its
Resolve handles secrets and ambiguity. This replaces
ConsumptionAuthority, which built a Resolved past the rule kit and
duplicated the grammar.
- LoadConsumption reads only the schema version and api.consumes, strictly.
A section that has nothing to do with consumption no longer fails a reader
that derives authority.
- The runtime projection (ConsumedAPI) carries the declared scopes.
- `scopes: []` is refused. An entry's revision can be bumped. A request
requires schema codefly.solution-manifest/v1, and v0 is still read.
- Slot marshals as {"from": ...} in JSON, so canonical bytes spell it as YAML
does.
- Fixtures use structural names.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
antoinetoussaint-byte
force-pushed
the
solution-consumption-authority
branch
from
October 12, 2026 18:28
4b58bf6 to
03d6e85
Compare
antoinetoussaint-byte
added a commit
to codefly-dev/cli
that referenced
this pull request
Oct 12, 2026
…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>
antoinetoussaint-byte
added a commit
to codefly-dev/cli
that referenced
this pull request
Oct 12, 2026
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>
antoinetoussaint-byte
added a commit
to codefly-dev/cli
that referenced
this pull request
Oct 12, 2026
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>
This was referenced Oct 12, 2026
antoinetoussaint-byte
added a commit
that referenced
this pull request
Oct 12, 2026
…solution's consumed APIs declare their scopes (#787) Carries, since v0.19.1: - #783 workcontext: StartInput/ChildInput take an issuer-supplied SessionID (empty mints a fresh one; a derived capability may not reuse its parent's). - #784 solution/manifest: solution-manifest/v1 — a consumed API declares `scopes` and `audience: {from: <group>/<key>}`, and the manifest derives the solution's authority request from them; the runtime projection carries the declared scopes. - #778 CI: config-defined hooks refused; git asked which filter each path gets. - #779, #780, #781, #786: dependency updates (Go modules, rust image, pydantic, GitHub Actions with the recorded job shapes and coverage-tool version). Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
gael-obin
pushed a commit
to gael-obin/cli
that referenced
this pull request
Oct 13, 2026
…s scoped api.consumes (codefly-dev#972) * feat(gitops): derive a composed solution's authority document from its 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> * style(gitops): drop the blank import line the contract import left Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(gitops): read only api.consumes for a solution's authority, and resolve 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> * style(gitops): index the consumption loops instead of copying each entry Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * chore(deps): pin core at main's merge of codefly-dev/core#784 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> * chore(conformance): declare the v0.19.2 core line the pin is on 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> --------- Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The delivery half of codefly-dev/module-saas-starter#1083, core's part. The render is codefly-dev/cli#972; the host half is codefly-dev/module-saas-starter#1084.
Why
A host bounds the capability a person's delegated client mints by what the consumer it was delivered to declares it consumes. It reads that from the consumer's registry grant, which is derived from the consumer's signed authority document. A renderer derives authority documents only from
module.contract.codefly.yaml. A composed solution declares what it consumes insolution.codefly.yaml'sapi.consumes, which named module, service and endpoint, and no audience or scopes. So a solution had no authority document at all.The shape (revised after review)
Manifest.ConsumptionContract(principal)turns the scoped entries into amodulecontract.Contract: one invoke binding per entry, with the entry's ID, its audience slot, its revision, and the declared scopes as explicit ceiling scopes. Namespaces, queues, ceilings and destinations are written as empty lists. The request is then judged by the module contract's own rules: grammar of kinds and actions, a slot key that names its meaning (*-audience/*-prefix), and revision. Its audience is resolved by the module contract's ownResolve, with the same secret and ambiguity refusals. Nothing is reimplemented; the earlier copied grammar and theResolvedbuilt outside the package's rules are gone.modelmay verifymodelservice. The earlier rule tyingastomoduleis gone, and so is "audience = composed name".LoadConsumption(data)reads the schema version andapi.consumes, strictly, and nothing else. A renderer that derives authority from the consumption must not fail on a section it does not read. A manifest thatLoadrefuses for its agent block, or for a section this core does not model, asks for no authority because of that.ConsumedAPI.Scopesis projected intoCODEFLY__API_CONSUMES, so a runtime can refuse a mint outside what it declared. The scopes are written once, in the YAML.scopes: [], rather than read as absent;modulecontract.SlotgainsMarshalJSON({"from": …}), so a manifest's canonical JSON spells a slot the way its YAML does.One open question for the owners: merging a solution's derived consumption with a module contract it also publishes, for example for its destinations. The CLI refuses that combination by name for now.
Classification: fix, at the owner.
API changes callers will see:
ConsumedAPInow holds a slice, so it is no longer comparable with==. The CLI's one test that compared it moves toreflect.DeepEqualin cli#972. solution-runtime-go has no such comparison in its tree; I checked with grep only, not a build.ConsumptionAuthority, from the first revision of this PR, is replaced byConsumptionContractand was never released.Tests
solution/manifest/consumption_authority_test.go, with structural fixtures intestdata/consumption/and no downstream names:as(the reviewer's case);ErrUnresolvedSlot;LoadandLoadConsumption;modulecontract.Parsedoes (the reviewer's case);LoadConsumptionreads an agentless manifest and one with an undeclared section, whichLoadrefuses, as asking for nothing, and gives the same contract asLoadfor a scoped one.Verified, and not
go test ./contracts/... ./solution/... ./internal/...andgo vet ./solution/manifest. Earlier full./...runs were green except two packages that pass alone and do not import this one.Order
mainas a pseudo-version. No core tag is cut here; a tag is a release and needs the owner's say-so. cli#972 explains why its conformance-matrix test needs a release line.🤖 Generated with Claude Code