Skip to content

feat(generate): runnables derives every method behind the hop, and every runnable is a tool - #1172

Merged
antoinetoussaint-byte merged 8 commits into
mainfrom
feat/tools-from-endpoints
Oct 26, 2026
Merged

antoinetoussaint-byte merged 8 commits into
mainfrom
feat/tools-from-endpoints

Conversation

@antoinetoussaint-byte

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

Copy link
Copy Markdown
Contributor

What we are trying to do

Make every endpoint RPC a runnable, and therefore a tool, with zero per-RPC work. The core half is in codefly-dev/core#843. This PR is the generator half: codefly generate runnables and runnable-bindings.

The change

  • generate runnables derives an operation for every served unary method that declares authority.hop.v1.policy. No proto edits are needed.
    • A read is written with "recovery": "RECOVERY_RECOMPUTE", empty lookup_scopes and no lookup.
    • A mutation is written with RECOVERY_RECEIPT, exactly the scopes its policy requires, no lookup scopes (its receipt is read under the invoke scopes by the caller that made the effect) and max_attempts: 1.
    • Every operation.json carries the tool's presentation.
    • Receipt lookups (Lookup<Op>, or another operation's lookup_method) are not derived.
  • Bounded names (r87 B1). A derived release name over 50 characters is shortened deterministically to a stable prefix and an 8-digit digest of the whole name. A long method name never fails generation. module-document-store authority/behind-the-hop now generates 110 operations. The refusal header no longer claims the methods carry the operation option.
  • The report (r87 M2). Each operation is printed with its origin (option or default) and effect. The methods left plain calls are listed with the reason. When anything was derived by default, the run gives the count, names the mutations, and states the grants rule and the codefly.runnable.v0.exclude opt-out:
    runnables: annotations-connect-annotate  /annotations.v1.AnnotationsService/Annotate  default  MUTATION  <digest>
    runnables: 16 method(s) derived by default from their authority policy, 8 of them mutations: …
    runnables: every operation is a tool, offered only to callers whose grants cover its binding; a method no agent may ever call declares `option (codefly.runnable.v0.exclude) = true;`
    
  • A method a default derivation cannot turn into an operation (a stream, any_checked_caller, Any, recursion) is listed as not an operation: … and never fails the command.
  • operation.json gains recovery. It is omitted when unstated, and runnable-bindings carries it into the prepared policy.
  • Core moves to 0c02ef0f (feat(update): pin an agent version, prereleases included, and report a dev build as one #843 head). The origin each operation reports is core's OperationSpec.Declared, not a second reading of the descriptor. The summary names each method once, even when it is published on several endpoints. composition.ProtobufProcedures(set, *endpoint) follows core generate proto: a template whose outputs sit beside --proto regenerates nothing (0.1.145 reports success) or is refused (0.1.160) #836's signature change.

Tests

  • TestGenerateRunnablesDerivesEveryMethodBehindTheHop carries the policy and exclude as unknown option bytes, as on disk. It covers:
    • a read, with recompute and no lookup;
    • a mutation, invoked under [write] only with no lookup;
    • an explicit option, kept;
    • a stream, listed rather than refused;
    • an excluded method, not derived;
    • a 55-character release name, bounded to 50 with its digest.
  • The document-store oracle and the saas source-operation tests follow the wire spelling: JSON names, int64 as strings, and every non-required key optional. The saas producer's committed package is held to the derived one modulo that re-spelling: the format must be int64/uint64, and array items are compared too. This holds until module-saas-starter regenerates.
  • Results. go test ./cmd/generate/ ./pkg/runnables/ ./cmd/show/... passes. golangci-lint --new-from-rev=5536d731 reports 0 issues.

Proof (obin-ai/module-annotations PR #65 at 3a9fcd6, no proto edits)

  • Derived tools. 16 per endpoint, 32 in total:
    • reads: GetAnnotation, GetThread, GetVocabulary, ListAnnotations, ListChanges, ListOnTarget, ListTargetKinds, ReadSelection;
    • mutations: Annotate, InstallVocabulary, Redact, Reply, ResolveThread, Retract, Revise, Select.
  • Bindings. runnable-bindings prepared all 32.
  • End-to-end call. A robin-style JSON call through the module's stand-in hop gave:
    • Annotate 200;
    • Annotate holding only read: 403 from the hop;
    • ListOnTarget 200.
  • Wire check. Both answers were walked key by key against their output contracts and conform (camelCase keys, int64 as strings).

Regeneration (expected drift, r87 M1 / decision 7)

Every module's --check gate goes red on this bump, and that is intended:

  • hand-marked operations gain a tool presentation;
  • contracts take the wire spelling, so package digests move;
  • modules behind the hop gain one operation per served method.

Regenerate per module and per composition environment:

codefly generate contracts <module>
codefly generate runnables <module>
codefly generate runnable-bindings --env <env>

A newly derived operation reaches a person only through grants. Add option (codefly.runnable.v0.exclude) = true; on anything no agent may ever call.

Follow-ups (not in this PR)

  • sdk-go:
    • Serve the generic Receipts/Lookup. It must admit a lookup only for the recorded ReceiptIdentity (owner and acting principal), holding the lookup scopes or else the invoke scopes, exactly as core's AdmitReceiptLookup decides.
    • Switch the receipt interceptor to core's derivation, so derived mutations can retry safely.
  • module-runtime: pkg/runnable.JSONSchema must learn NUMBER, JSON, format and values, and mint a lookup under the invoke scopes when lookup_scopes is empty.
  • robin: the reader changes listed for robin's implementer.

Classification: fix at the owner (core plus the CLI generator). No hack.

🤖 Generated with Claude Code

antoinetoussaint-byte and others added 8 commits October 25, 2026 21:11
…ery runnable is a tool

`codefly generate runnables` now derives an operation for every served unary
method that declares authority.hop.v1.policy, with nothing added to the proto
(core feat/tools-from-endpoints). A read is written with
recovery: RECOVERY_RECOMPUTE and no lookup; a mutation with RECOVERY_RECEIPT
and the generic receipt lookup. Every operation.json carries the tool's
presentation. A method a default derivation cannot turn into an operation (a
stream, any_checked_caller, Any, recursion) is listed as "not an operation"
and never fails the command; a marked method is refused exactly as before.

operation.json gains `recovery` (omitted when unstated, so existing documents
are byte-identical), carried through runnable-bindings into the prepared
policy. Moves core to 45383f9f, which also needs ProtobufProcedures' new
endpoint argument (core #836).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scopes

Moves core to 87497b44 (codefly-dev/core#843): a derived mutation is written
with exactly the scopes its authority policy requires and no lookup scopes,
its receipt read under those invoke scopes. The default-derivation test now
holds both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… report of what became a tool (r87)

r87 B1 — a derived release name over 50 characters is shortened
deterministically to a stable prefix and an eight-digit digest of the whole
name (core BoundedName), so a long method name never fails generation
(module-document-store's GetDocumentTransformationReceipt and friends now
derive). The refusal header no longer claims the methods carry the operation
option: "N method(s) cannot be derived as runnable operations".

r87 M2 / decision 6 — the run reports each operation's origin (option or
default) and effect, the methods left plain calls and why, and, when any
operation was derived by default, their count, the mutations among them, the
grants rule and the codefly.runnable.v0.exclude opt-out.

r87 m1 — the default-derivation test covers exclude carried as unknown option
bytes (51004) and a release name past 50 characters.

r87 B2 — moves core to 483255ec (core#843 with r86 fixed). The source-operation
and document-store oracle tests follow the wire spelling (JSON names, int64 as
strings): the producer's committed package is held to the derived one modulo
that re-spelling until module-saas-starter regenerates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…no Go change)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ghter bridge (r102)

r102 m1 — the summary counts and names methods, each once, and a notice prints
once per method, though one method is an operation per endpoint.
r102 m2 — the origin is core's OperationSpec.Declared; declaresOperation, a
second mechanism for the same fact, is gone.
r102 m3 — requireSameModuloWireSpelling requires format int64/uint64 on an
INTEGER that became a STRING, every non-required key optional, and recurses
into array items.
r102 m4 — core at 0c02ef0f (core#843 with r100's majors fixed): every
non-required key optional on the wire, so the document-store oracle pin moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ence regenerated

Re-pins core to v0.19.7-0.20261026023455-8b5ddef6d55a, #843 as merged.
That commit also carries core#845 (WorkScopeV1.resource_owner_id) and
core#835's module-contract kit fixture roles-grant-bindings.

- docs/cli-reference.md: regenerated with
  `go test ./cmd -run TestCLIReferenceIsCurrent -update-reference` for this
  branch's new `generate runnables` help text.
- The round-trip test exempts WorkScopeV1.resource_owner_id by name: core
  refuses an operation that declares an owner, so the document has nothing to
  carry. TestRunnablesRefusesADeclaredResourceOwner proves the refusal reaches
  `codefly generate runnables` with core's reason.
- The module-contract kit's staging composition supplies the audiences
  roles-grant-bindings resolves (the same line as #1167, so the two merge
  cleanly).

The document-store oracle digest is unchanged (4b72d393...).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	go.mod
#	go.sum
#	pkg/gitops/wire_kits_test.go
@antoinetoussaint-byte
antoinetoussaint-byte added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 3c7ab6b Oct 26, 2026
14 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.

1 participant