Skip to content

spec: @http gains the operation surface; M8 design gaps resolved (#173) - #175

Merged
trendvidia merged 1 commit into
mainfrom
spec/openapi-operation-surface-173
Jul 25, 2026
Merged

spec: @http gains the operation surface; M8 design gaps resolved (#173)#175
trendvidia merged 1 commit into
mainfrom
spec/openapi-operation-surface-173

Conversation

@trendvidia

Copy link
Copy Markdown
Owner

First slice of #173: resolve the three M8 design gaps GH #93 left open, so implementation has settled ground. No generator code yet — decisions land as reviewable spec text first, as with #93/#111/#155.

Decision record: docs/RFC-001-issues.md §#80.

Gap 1 — operation surface: enrich canonical @http

annotation http(
  method: string,
  path: string,
  summary: string = "",
  operation_id: string = "",
  tags: any = [],
  security: any = []
);

Binding rules are pinned alongside the signature: {name} path segments bind to same-named top-level request fields; remaining fields bind to the query string for bodyless methods and to the request body otherwise; operation_id defaults to <Service>_<Method> (unique by construction); summary falls back to the first sentence of @description.

tags and security take list literals of strings because the grammar admits no repeated parameter type (§5.1 paramType ::= qualifiedIdent), so list-shaped values ride any plus a Literal.list (§8.1). I verified this against the reference parser before writing the text — the lowered carrier is:

args:{name:"tags" literal:{list:{elements:{string_value:"orders"} elements:{string_value:"public"}}}}

Security-scheme definitions stay in generator configuration, per the §9.4/#112 argument that keeps deployment topology out of descriptors that cross org boundaries.

Recorded trade-off. The alternative was a generator-owned openapi.* annotation library with zero spec involvement (gnostic-style). It was considered and not chosen; the cost is written into the record rather than elided: OpenAPI vocabulary now lives in the library every port mirrors and the IETF draft describes, and the surface can only grow additively from here. What makes it tolerable is that the parameters carry no validation semantics and impose no port obligation beyond carrying them through the §8.1 carrier — a port that renders no REST surface parses them and interprets nothing.

No responses parameter. Responses are derived — success from the method's return type, errors from @error_code plus the §7 report model, which the settled schema half already maps. Authored per-status entries would need a list of message literals: the carrier represents that shape (LiteralValue.literal), but the reference parser rejects it at an annotation argument today, so specifying it would put the spec ahead of the toolchain that can express it. Revisit when the parser gap closes and demand exists.

Gap 2 — audience/visibility tiers

Taxonomy is protowire.docs.v1.Audience from #170. Three sub-decisions:

  • Filtering, never stripping. --audience <tier> emits only elements at or below the tier; descriptors are never rewritten. Stripping has spec implications — what a conformant consumer may assume about an image's completeness — and needs its own design pass, which nothing yet demands.
  • Tiers come from generator configuration (FQN globs → tier, default PUBLIC), not a new canonical annotation: visibility is deployment policy, the same reasoning that rejected @encrypted in Schema-level encryption / key-reference annotation — @encrypted(key_ref) (deferred from §6.7) #112. Doc-pack topics anchoring an element contribute their tier, so documentation and API surface cannot disagree.
  • Transitive inconsistency is an error. A PUBLIC element whose closure reaches an INTERNAL one fails generation, naming both ends — a dangling $ref or a silently inlined restricted definition are both worse than refusing.

Gap 3 — availability version

x-since is derived from protoregistry history (first registered revision containing the element), omitted when no registry is configured. No canonical @since is added: an authored availability claim is unverifiable and nothing in the toolchain would check it, whereas the registry already knows. Org-namespaced @since stays user-space and is ignored.

Additivity

Every added parameter is defaulted, so @http("GET", "/orders") keeps its v1.2.0 meaning and no existing schema changes shape. New fixture 21_http_operation.proto pins it by compiling the bare two-argument form beside fully-parameterized uses, a named subset after the positional pair, and a {name} template. No grammar production, extension number, wire, or report change; annotations.proto stays parseable by any v1.2 parser.

Full suite green, including the positive corpus byte-stability check that now covers the new fixture.

Follow-ups

Refs #173. Doc pack input: #170 / PR #174.

Resolves the three design gaps GH #93 left open for the OpenAPI
boundary renderer, now that #170 (PR #174) has landed the doc pack it
reads. Decision record: docs/RFC-001-issues.md §#80.

Gap 1 — operation surface. @http gains four defaulted parameters:
summary, operation_id, tags, security (§5.2, annotations.proto). Binding
rules are pinned with them: {name} path segments bind to same-named
top-level request fields; remaining fields bind to the query string for
bodyless methods and to the request body otherwise; operation_id
defaults to <Service>_<Method>, unique by construction; summary falls
back to the first sentence of @description. tags and security take list
literals of strings, because the grammar admits no repeated parameter
type (§5.1 paramType ::= qualifiedIdent) — list-shaped values ride `any`
plus a Literal.list (§8.1), verified against the reference parser before
the text was written. Security-scheme *definitions* stay in generator
config, per the §9.4/#112 argument that keeps deployment topology out of
descriptors.

The alternative — a generator-owned openapi.* library with zero spec
involvement — was considered and not chosen; the cost is recorded rather
than elided: OpenAPI vocabulary now lives in the library every port
mirrors and the draft describes, and it can only grow additively. What
makes it tolerable is that the parameters carry no validation semantics
and impose no port obligation beyond carrying them through the §8.1
carrier.

No `responses` parameter. Responses are derived — success from the
return type, errors from @error_code plus §7 — and authored per-status
entries would need a list of message literals: a shape the carrier
represents (LiteralValue.literal) but the reference parser rejects at an
annotation argument today. Specifying it would put the spec ahead of the
toolchain that can express it.

Gap 2 — audience/visibility tiers. Taxonomy is protowire.docs.v1.Audience
(#170). Artifact filtering only, never descriptor stripping (stripping
has spec implications about image completeness and needs its own pass).
Tiers come from generator configuration, not a new canonical annotation,
because visibility is deployment policy — same reasoning as #112 — with
doc-pack topics contributing their own tier so docs and API surface
cannot disagree. Transitive inconsistency (public reaching internal) is
an error: a dangling $ref or a silently inlined restricted definition
are both worse than refusing.

Gap 3 — availability version. x-since is derived from protoregistry
history, omitted when no registry is configured. No canonical @SInCE is
added: an authored availability claim is unverifiable and nothing would
check it, while the registry already knows.

Additive throughout: the v1.2.0 two-argument @http keeps its meaning and
no existing schema changes shape, pinned by new fixture
21_http_operation.proto, which compiles the bare form beside fully
parameterized uses. No grammar production, extension number, wire, or
report change.
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