Skip to content

feat: the interface artifact marks every read as a Runnable operation (x-codefly-operation) - #77

Merged
antoinetoussaint-byte merged 1 commit into
mainfrom
feat/operation-marker
Oct 27, 2026
Merged

antoinetoussaint-byte merged 1 commit into
mainfrom
feat/operation-marker

Conversation

@antoinetoussaint-byte

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

Copy link
Copy Markdown
Contributor

Summary

Second of three PRs for codefly-dev/cli#1184. The interface artifact now publishes x-codefly-operation beside x-codefly-authority-policy for every read a backend serves, so codefly generate runnables derives each one as a Runnable — and so a tool — exactly as it derives a module's methods.

  • The marker is core's, not the runtime's. operationMarker hands core the policy JSON the operation already publishes and gets back the marker (corerunnable.RouteMarker, feat(runnable): a REST read over GET is an operation, and Swagger 2.0 is read as itself core#852): core's default bounds, recomputed on an uncertain outcome, invoked under exactly the scopes the policy requires. The tool's name is the operationId, its description the Summary the operation is required to carry, and the audience Manifest.ID — the principal this runtime's hop already checks a Work Context against (dialAuthority(…, s.manifest.ID, …)). No vocabulary is invented here. Going through RouteMarker with plain JSON also keeps this package from constructing core's wire messages, which TestNoCapabilityIsConstructedOrDecodedByType / TestNoWorkContextImplementationGrowsHere refuse (my first version did and they caught it).
  • Only reads are marked — a deliberate narrowing of "every operation that declares a Policy". A mutation is resolved by its effect receipt, read through a receipt route (<path> + the receipt suffix) that this runtime does not serve; marking one would publish a tool whose uncertain outcome nothing can resolve. So /record, /link/begin, /chat/install become nothing, not write tools. Also unmarked: any-checked-caller routes (nothing to mint a capability from), streams, the MCP route (a JSON-RPC envelope, not one operation's input) and relayed routes (another module's call, a tool through that module's own derived operation). A relayed subtree is a path template, which core also refuses.
  • Operation.Exclude keeps a read from every agent, as codefly.runnable.v0.exclude keeps a module's method. A marked read core cannot carry is refused by generate runnables naming the route, not silently left unmarked; the owner fixes the shape or excludes it.
  • Every defined object closes additionalProperties. Core's bounded profile refuses an object that does not, so without this nothing the runtime publishes would project. It is true of what is described: a struct marshals exactly its fields, and protojson refuses an undeclared one.
  • The InterfaceDocument trap is named, not refused. InterfaceDocument (the free function) marks nothing — only the Server knows its audience — so a solution committing its output would publish no tool, silently. README (a callout in "Documented operations"), CHANGELOG and its doc comment now say: commit Server.InterfaceArtifact's output, never InterfaceDocument's. I did not make it refuse: its one outside caller (obin-ai/module-evaluation libraries/evaluation-client/go/runner/codefly/codefly_test.go:65) uses it to validate an operation, and a refusal would turn every read into an error for documentation-only use. It would also miss the other way to lose every marker, a solution still on a runtime from before this PR. The guard is in feat(generate): a solution's routes become Runnables, and so tools, as a module's methods do (#1184) cli#1185 instead, where the document is read: a document that marks no route although its routes declare read-only policies gets a "not an operation" notice per read, naming Server.InterfaceArtifact as the fix.
  • Core pinned to v0.19.7-0.20261026182446-518714d046af, feat(runnable): a REST read over GET is an operation, and Swagger 2.0 is read as itself core#852's merge commit on main; the go line moves to 1.27.3 because that core requires it.

Classification

Fix at the owner: the runtime owns the artifact a solution publishes. No stopgap.

Docs

README "Documented operations" (every read is a Runnable operation; what is not marked; Exclude; closed objects), CHANGELOG Unreleased entry.

Blast radius — a regeneration PR in obin-ai/core-solutions

The artifact is committed in consumer repos and CI re-generates it, so bumping to this release changes every consumer's openapi/api.swagger.json and contracts/api/backend/http/openapi.json (every definition gains additionalProperties: false; reads gain the marker). On #451's head, the wiki would gain markers on GET /v1/wiki/upload-access and GET /v1/wiki/upload-configuration (both wiki.page:read); proposals has no own read. I have not opened that regeneration PR: obin-ai/core-solutions#451 (open, not a draft) touches exactly those files, and it should land first. The releases solution is on runtime v0.0.15 on main and declares no policies yet, so its reads become tools once it moves onto the authority runtime.

Test plan

  • go build ./... && go vet ./... && go test -race ./... -count=1 and gofmt -l . on the 518714d0 pin — green (CI's exact steps).
  • New runnable_marker_test.go: a releases-shaped server's InterfaceArtifact marks exactly its two GET reads, and core derives each from the artifact (PackageFromOpenAPIOperation): the query is the input, read-only, recomputed, described by the Summary; the write, the datasource link and the status are no operation; Exclude unmarks; InterfaceDocument marks nothing; every definition is closed.
  • End to end (scratch workspace, recorded on feat(generate): a solution's routes become Runnables, and so tools, as a module's methods do (#1184) cli#1185): releases' real response types and operation list from core-solutions main (6edcb4b7) declared with policies on this branch → artifact marks only GET /v1/releases/history and GET /v1/releases/recorded → the CLI built from codefly-dev/cli's PR runs generate contracts, generate runnables, generate runnable-bindings.

Not verified

Part of codefly-dev/cli#1184.

🤖 Generated with Claude Code

… (x-codefly-operation)

Beside the authority policy of each operation whose policy requires only
read, Server.InterfaceArtifact now writes core's x-codefly-operation marker,
which core itself derives from that policy (runnable.RouteMarker, core#852).
`codefly generate runnables` derives a Runnable package — and so a tool —
from every marked route, as it does from every method a module serves behind
the hop.

- Operation.Exclude keeps a read from every agent, as codefly.runnable.v0.exclude
  keeps a module's method. The MCP route and every relayed route carry it.
- InterfaceDocument marks nothing: the marker names the audience the serving
  runtime's hop admits, which only the Server knows. A solution must commit
  Server.InterfaceArtifact's output, and the README and CHANGELOG say so.
- Every defined object closes additionalProperties, which core's bounded
  profile requires of a fixed shape.
- Core moves to v0.19.7-0.20261026182446-518714d046af (core#852 merged).

Rebased onto main after #78: main's authority-client and hop pins and its
Go 1.27.3 bump are kept, core alone moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte merged commit 29c18c3 into main Oct 27, 2026
1 check 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.

1 participant