Skip to content

feat(ir)!: mark inbound operations and give a path its own docs - #1366

Open
OmarAlJarrah wants to merge 10 commits into
feat/ir-type-node-factsfrom
feat/ir-inbound-operations
Open

OmarAlJarrah wants to merge 10 commits into
feat/ir-type-node-factsfrom
feat/ir-inbound-operations

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

Three facts about where an operation sits and who sends it had no field.

  • Callbacks are marked inbound, and named (ir: callback operations aren't marked as inbound, and a callback has no name #625). A callback operation sat beside the client's operations with isWebhook: false. Only a reverse lookup through its declaring operation's Callbacks showed that the API sends it, and the callback's name lived only inside an operation ID. Every consumer reads an inbound operation the other way round: read-only and write-only projections flip, and so does a type's usage. HTTPBinding.IsWebhook becomes Inbound, a closed webhook | callback, empty for an operation the client sends. The OpenAPI compiler marks every callback operation, and Callback gains Name, the callbacks-map key. irverify holds both halves of a callback to agree: a marked operation nobody lists (ir/callback-unlisted) and a listed one that is not marked (ir/callback-unmarked) are each reported, and so is an undeclared kind (ir/unknown-http-inbound). docs/ir/http-binding.md#inbound-operations replaces the webhook section.
  • An inbound operation inherits no auth (ir: decide whether webhook and callback operations inherit the service's auth #626). A webhook writing no security inherited the service's requirement, the client's API key, though the API sends the request and the receiver never sees that key. docs/ir/auth.md#inbound-operations decides it: an inbound operation's Auth is what its own declaration states, read as how the API authenticates to the receiver, and nil states none. Both alternatives are weighed there. Inheriting matches every other operation, and is wrong for the one direction the requirement does not cover. Refusing an inbound operation any Auth drops what a document says about how deliveries are signed. No lowering changes, since the compiler already wrote nil; the meaning of that nil does.
  • A path's own documentation has a field (ir: decide whether a path item's summary and description get a typed home #1319). HTTPBinding.PathDocs holds a Path Item's summary and description. It is written on every operation the path mounts, as URITemplate is, and read layer by layer so a $ref site's text stands in front of its target's. The openapi:pathItem/summary and openapi:pathItem/description entries an operation held, with their info report, are retired; a path item mounting no operation still keeps its pair verbatim on the nearest node, as it keeps everything else it writes. The issue weighed three homes. A second Docs on the operation would sit empty in every format without paths. A path entity would make every route a node to hold two strings. Declining would leave the text under a key named for one format. The binding is where the route already lives, and a format that documents its grouping construct writes OperationGroup.Docs or Channel.Docs instead.
  • With a name to use, a callback's correlation key is now its declaring operation's key, its name and its method. A rewritten runtime expression therefore keeps the key.

The golden spec has no webhooks, no callbacks and no path-item documentation (spec @319dc756), so its IR changes only in isWebhook becoming absent. That is the rule-3 ranking all three issues carry.

Closes #625
Closes #626
Closes #1319

Breaking

IRVersion moves to 0.32.0, after #1361's 0.31.0 on main's 0.30.0. Earlier commits name 0.29.0 and 0.30.0, from before #1333 and #1327 landed; the merges renumbered. A consumer pinned to the generation before this one refuses the document. It also finds no isWebhook, and no openapi:pathItem/summary or openapi:pathItem/description entry on an operation, and one that read a webhook with no Auth as inheriting now reads it as stating none.

Stack

Base: #1361, on #1355, #1354, #1351 and the emitter-design stack. Merge in order.

#1322 and #1327 landed on main while this was open. Its merge of them keeps their structure: a mount computes what its path item keeps once and copies it onto each operation, and the path's documentation is computed with it and written into each operation's PathDocs. An unmounted item keeps its pair as #1322 does, so precedence takes no docs flag.

Test plan

  • go test on ir/..., compilers/..., cmd/..., engine/..., pass/... and internal/.... The goldens, shape pin and census were regenerated.
  • New or rewritten tests:
    • irverify.TestVerify_UndeclaredInboundKindIsAViolation, and TestVerify_ACallbacksTwoHalvesAgree (six rows, a dangling listing included).
    • TestVerify_WebhookRouteIsInTheFieldItsFlagSelects gains the callback rows.
    • operation.TestCallbacks_NameWithSlashKeepsItsOwnKey now asserts each callback's name, listing and mark.
    • operation.TestPathItem_DocsKeptOnEveryRoute, TestPathItem_DocsAddNoMarker and TestApplyPathItemDocs_WritesTheBindingItIsGiven.
    • The $ref-sibling, collision, chain and YAML-merge cases now read PathDocs.
  • Mutants, each reddening a test: the callback name dropped, the callback mark dropped, and the description not written. Ignoring cedes for the summary is an equivalent mutant: layers apply backward, so the use site writes last and wins anyway. The check stays because it states the precedence rather than leaning on that order.
  • TestVerify_ACleanComponentCostsNothing first caught the callback check spelling a path for every operation. It now spells one only for a marked operation and a listing entry.
  • Push checklist: subjects within 72 characters with the number, no artifact hits, skills_lint clean.
  • CI.

A callback operation sat beside client operations with nothing saying
the API sends it: only a reverse lookup through its declaring
operation's Callbacks showed it, and the callback's name lived only
inside an operation ID. A webhook inherited the service's security
requirement, the client's API key, though the API sends it. And a path
item's summary and description had no field, so they were kept under
Unmodeled on each operation with an info report.

- HTTPBinding.IsWebhook becomes Inbound, a closed webhook | callback,
  empty for an operation the client sends. The OpenAPI compiler marks
  callback operations, and Callback gains Name, the callbacks-map key.
  irverify reports an undeclared kind (ir/unknown-http-inbound) and a
  callback whose two halves disagree (ir/callback-unlisted,
  ir/callback-unmarked).
- An inbound operation inherits no requirement: its Auth is what its
  own declaration states, read as how the API authenticates to the
  receiver (docs/ir/auth.md#inbound-operations).
- HTTPBinding gains PathDocs, the path's own documentation, written on
  every operation the path mounts as URITemplate is. The
  openapi:pathItemSummary and openapi:pathItemDescription keys and
  their report are retired.
- A callback's correlation key now uses its name and method, so a
  rewritten runtime expression keeps it.

BREAKING CHANGE: IRVersion moves to 0.29.0. A consumer pinned to 0.28.0
refuses the document, finds no isWebhook, and no longer finds the
path-item keys. One that read a webhook with no Auth as inheriting now
reads it as stating none.
#1325 took 0.28.0 on main and #1361 moved to 0.29.0, so this branch's
generation is 0.30.0: the constant, its generations row with the shape
recomputed, and its version-history entry.
checkCallbacks walked each operation's bindings inline, past the
cognitive-complexity cap, behind a type check its own type assertion
already makes, which left a branch no walk can take. The walk now asserts
the type once and hands the operation to noteCallbacks.
OmarAlJarrah and others added 5 commits November 2, 2026 18:02
…at/ir-inbound-operations

# Conflicts:
#	compilers/openapi/e2e/conformance_pathitems_test.go
#	compilers/openapi/e2e/conformance_unmodeled_test.go
#	compilers/openapi/internal/operation/pathitem.go
#	compilers/openapi/internal/operation/pathitem_internal_test.go
#	compilers/openapi/internal/operation/pathitem_test.go
#	compilers/openapi/internal/operation/pathitemref.go
#	compilers/openapi/internal/operation/pathitemref_collisions_test.go
#	compilers/openapi/internal/operation/pathitemref_fields_test.go
#	compilers/openapi/internal/operation/pathitemref_test.go
#	docs/compilers/openapi.md
#	ir/document.go
#	testdata/conformance/openapi/path-item-docs.golden.json
#	testdata/conformance/openapi/path-item-ref-siblings.golden.json
#	testdata/conformance/openapi/yaml-merge-keys.golden.json
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