Uh oh!
There was an error while loading. Please reload this page.
feat: enforce per-method effect/network policy on the Solution contract (#289) - #292
Merged
antoinetoussaint-byte merged 3 commits intoAug 16, 2026
Conversation
…ct (#289) Bind a solution_method_policy extension (network reach + state effect ceiling) to every Solution RPC, mirroring provider_method_policy, and add the host-side enforcement point that reads and enforces it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…only invariant Review found the policy control was framed as effect enforcement against the plugin, when it only gates whether the host dispatches a call. The Solution contract has no host-brokered callback path (unlike provider's ProviderHost), so a plugin's real filesystem/registry writes are unmediated — the gate cannot constrain them. Reword the package and interceptor docs to say what the gate does and does not do, and name the #290 wiring as its integration point so it isn't silently left uninstalled. The interceptor is unary-only; a future streaming Solution RPC would dispatch unchecked. Add TestSolutionContractIsUnaryOnly so adding one fails the build until a stream gate exists, instead of silently bypassing enforcement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… per-call ceiling
Review pushback was right: the previous refutations were dodges. Fixing the
real causes.
Finding 2 (no consumer): the host's single agent-connection factory is
agents/manager.loader (grpc.NewClient), and the connection it returns is
long-lived and reused across operations (AgentConn.GRPCConn). The interceptor
was built in a vacuum against neither fact. A ceiling baked in at dial time is
therefore the wrong shape — the ceiling belongs to the operation, which varies
per call over one shared connection. Carry it in the call context
(WithCeiling/CeilingFrom), install EnforcingClientInterceptor in the loader dial
options so it is genuinely wired (no-ops for non-Solution methods, safe on every
agent), and fail closed when a Solution RPC carries no ceiling.
Finding 4 (correlated axes): keeping two axes is only honest if they cannot go
incoherent. Add TestSolutionMethodPolicyAxesAreCoherent asserting registry
network iff registry effect, so an annotation like {OFFLINE, REGISTRY_WRITE}
(push while offline) fails the build instead of being silently accepted by the
ordered ceiling checks.
Finding 5 (reproducibility): verified empirically that committed v1.6.1 and
freshly regenerated v1.6.2 differ only in the header line — bodies byte-
identical — so matching #288's baseline is correct, not a Frankenstein file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>antoinetoussaint-byte
merged commit Aug 16, 2026
ca6e289
into
issue-287-new-agent-kind-codefly-solution-registry-entry-proto4 tasks
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 freeto 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.
Closes#289.
Stacked on #288 (adds the
Solutioncontract); base this PR onissue-287-...until #288 merges, then it retargetsmain.Summary
SolutionRPCs (Packagepushes an OCI artifact,Render/Create/Updatemutate the filesystem) previously documented their side effects in comments only — the decorative-annotation smell New agent kind: codefly:solution — registry entry, proto enum, manifest schema, proto service #287 deliberately avoided. This binds a machine-readable ceiling to each RPC and enforces it at the host's dispatch boundary.solution_method_policyextension mirroringprovider_method_policy, with solution-specific vocabularies (SolutionNetworkMode,SolutionEffect) ordered so a host ceiling admits every mode/effect at or below it. Each RPC is annotated with its true network reach and state effect.solution.EnforcingClientInterceptoris installed inagents/manager.loader'sgrpc.NewClientdial options — the single factory for every agent connection. It reads each outgoing Solution RPC's declared policy and the ceiling stamped on the call context (solution.WithCeiling), and fails the call closed (PermissionDenied) before it crosses the wire when the declared ceiling exceeds the admitted one, or when no ceiling is present. It no-ops for every non-Solution method, so it is safe on all existing agent connections.Ceiling is per-call, not per-dial
AgentConn.GRPCConnhands out one long-lived connection reused across operations, so the admitted ceiling belongs to the operation (the call), not the transport (the dial). The interceptor therefore reads the ceiling from the call context; a Solution RPC issued without one is refused.What this gate does and does not do
The gate constrains what the host chooses to invoke — it does not, and under this contract cannot, constrain what a solution executor actually does inside a handler. Unlike
provider.proto(whereProviderHostbrokers the provider's side effects and the host enforces at the point of effect), the Solution contract has no host-brokered callback path, so a plugin's real filesystem/registry writes are unmediated. Enforcing declared effects against plugin behavior would require a broker this contract does not define.Test plan
go build ./...go test -race ./solution/...— contract test asserts every RPC carries the expected policy;TestSolutionContractIsUnaryOnlyguards the invariant the unary-only gate relies on;TestSolutionMethodPolicyAxesAreCoherentasserts registry-network ⟺ registry-effect so an incoherent annotation (e.g. push-while-offline) fails the build; admission unit tests cover both axes and every fail-closed path; a real bufconn gRPC server verifies an over-ceilingPackageand a no-ceilingCreateare denied before reaching the server while an at-ceilingCreateis admitted; a pass-through test confirms non-Solution methods dispatch with no ceiling.go test ./agents/manager/— the dial-path change leaves existing agent loading green (interceptor no-ops for non-Solution methods).go test ./provider/— provider contract unaffected.codefly generate proto --localreproduces the committed generated code. Verified empirically that the committedsolution_grpc.pb.goand a fresh regen differ only in theprotoc-gen-go-grpcheader line (v1.6.1committed vsv1.6.2locally installed) — bodies are byte-identical — so the header is reverted to match feat: add codefly:solution agent kind (#287) #288's baseline rather than churn 14 unrelated_grpc.pb.gofiles. The go-grpc version pin lives in thecodeflybinary, not this repo.