Design-time lint

Most checks in Bowire run against a call: you invoke a method and assert on what came back. Lint runs against the surface: it reads the discovered schema and reports design problems without sending a single request to the API under review.

That makes it the one check you can run on a schema in a pull request, before the service it describes exists.

# a live service
bowire lint https://api.example.com --protocol rest

# a snapshot captured earlier — the shape a pipeline uses
bowire lint api-snapshot.json --format markdown --output lint.md

<source> is either a .json snapshot from bowire diff snapshot or a live URL to discover. --protocol names the plugin for a live URL and is guessed from the URL scheme when unset.

The rules

Rule id Severity Fires when
BWR-LINT-SENSITIVE-RESPONSE High a response field looks like a secret
BWR-LINT-PII-RESPONSE Medium a response field looks like personal data — email, phone, SSN, date of birth, address, passport or tax id
BWR-LINT-MISSING-PAGINATION Medium a method returns a list and takes no page / limit / offset / cursor parameter, and its response carries no continuation token
BWR-LINT-STRING-TIMESTAMP Low a timestamp ships as a bare string rather than a typed instant
BWR-LINT-MISSING-VERSIONING Low the service declares no version and no route carries a version marker such as /v1/

What lint can see, and what it cannot

A rule can only inspect what discovery produced. This is the single most important thing to know before reading a lint result:

Protocol Request fields Response fields Rules that can fire
gRPC (reflection or descriptor set) yes yes all five
REST (OpenAPI document) yes yes, where the operation declares a 2xx JSON response schema all five
REST (embedded, ApiExplorer) yes yes, where the endpoint declares its response type all five

Against a gRPC target the descriptors carry full message types, so every rule evaluates. Against a REST target, the response shape comes from the OpenAPI operation's 2xx application/json schema — a top-level array is read as one repeated items field of the element type, so a list endpoint reads as a list to the pagination rule — or, when Bowire runs inside the host, from the endpoint's declared response type. An endpoint that declares neither (a Minimal API handler returning a bare Results.Ok(...) with no Produces<T>(), an OpenAPI response with a description and no schema) has no response shape for the four response-shaped rules to read.

Lint says so. When any method has no response shape, the report carries a note under the summary rather than passing it silently:

no findings
note: 3 of 16 methods declare no response schema; the response-shaped rules
      (sensitive and PII fields, pagination, string timestamps) could not
      evaluate those. For REST, annotate the endpoint's response type
      (Produces<T>, or a response schema in the OpenAPI document).

The remedy is on the API side: declare the response type, and the rules evaluate it on the next run.

A worked example against the gRPC sample:

$ bowire lint http://localhost:5183 --protocol grpc
[MEDIUM] BWR-LINT-MISSING-PAGINATION  …Greeter.SayHelloBatch  Method 'SayHelloBatch'
         returns a list but takes no pagination parameter (page / limit / offset /
         cursor). Unbounded list responses are a scaling and denial-of-service risk.
[LOW]    BWR-LINT-MISSING-VERSIONING   …Greeter  Service declares no version and no
         route carries a version marker (e.g. /v1/).
[LOW]    BWR-LINT-MISSING-VERSIONING   grpc.reflection.v1alpha.ServerReflection  (same)

3 findings (1 medium, 2 low)

Configuration

Severities and on/off switches live in .bowire/rules.json, discovered by walking up from the working directory the way a linter should — so a repository configures its rules once and every checkout, and the pipeline, read the same file.

bowire lint https://api.example.com --rules .bowire/rules.json

Pass --rules to point at a specific file; omit it to auto-discover.

Gating a pipeline

--fail-on turns the report into a gate:

bowire lint https://api.example.com --protocol rest --fail-on medium
Value Exit non-zero when
none (default) never — the command always exits 0
info / low / medium / high a finding reaches that severity

The default is none deliberately: adopting lint never breaks a pipeline on the first run. Start at high, and lower the bar as findings get fixed rather than the other way round.

--format json or markdown with --output <file> writes a report a later step can pick up — including bowire report rollup, which reads lint output alongside contract, benchmark, scan and test reports.

In the workbench

The Lint rail runs the same rules over the active workspace's discovered surface and lists the findings, each one clickable through to the method it fired on. With nothing discovered yet the rail shows an empty state rather than an empty list.